<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"><title>Dubbo 源码分析 – 服务引用 | 云少IT</title><meta name="keywords" content="Dubbo 源码分析 – 服务引用"><meta name="author" content="云少"><meta name="copyright" content="云少"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-touch-fullscreen" content="yes"><meta name="apple-mobile-web-app-title" content="Dubbo 源码分析 – 服务引用"><meta name="application-name" content="Dubbo 源码分析 – 服务引用"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="#ffffff"><meta property="og:type" content="article"><meta property="og:title" content="Dubbo 源码分析 – 服务引用"><meta property="og:url" content="https://it985.github.io/posts/d72547db.html"><meta property="og:site_name" content="云少IT"><meta property="og:description" content="四、Dubbo 源码分析 – 服务引用1. 简介在上一篇文章中，我详细的分析了服务导出的原理。本篇文章我们趁热打铁，继续分析服务引用的原理。在 Dubbo 中，我们可以通过两种方式引用远程服务。第一种是使用服务直联的方式引用服务，第二种方式是基于注册中心进行引用。服务直联的方式仅适合在调试或测试服务"><meta property="og:locale" content="zh-CN"><meta property="og:image" content="https://www.bing.com/th?id=OHR.YiPeng_EN-GB1679352084_UHD.jpg"><meta property="article:author" content="云少"><meta property="article:tag" content="云少IT,IT,技术,分享,程序员,博客,教程,工具,框架,bug,java,spring,数据库,"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://www.bing.com/th?id=OHR.YiPeng_EN-GB1679352084_UHD.jpg"><meta name="description" content="四、Dubbo 源码分析 – 服务引用1. 简介在上一篇文章中，我详细的分析了服务导出的原理。本篇文章我们趁热打铁，继续分析服务引用的原理。在 Dubbo 中，我们可以通过两种方式引用远程服务。第一种是使用服务直联的方式引用服务，第二种方式是基于注册中心进行引用。服务直联的方式仅适合在调试或测试服务"><link rel="shortcut icon" href="/img/logo.webp"><link rel="canonical" href="https://it985.github.io/posts/d72547db"><link rel="preconnect" href="//npm.elemecdn.com"><link rel="preconnect" href="//npm.onmicrosoft.cn"><link rel="preconnect" href="//www.google-analytics.com" crossorigin=""><link rel="preconnect" href="//busuanzi.ibruce.info"><meta name="google-site-verification" content="NuBZ4r-QCqSgo4XUScdEsQW0bolIHEiVGq4A16ndPQA"><meta name="baidu-site-verification" content="code-xxx"><meta name="msvalidate.01" content="xxx"><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.cbd.int/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.css" media="print" onload='this.media="all"'><link rel="stylesheet" href="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/swiper/swiper.min.css" media="print" onload='this.media="all"'><script async src="https://www.googletagmanager.com/gtag/js?id=G-3VMKW5TZBM"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-3VMKW5TZBM")</script><script>const GLOBAL_CONFIG={linkPageTop:{enable:!0,title:"与数百名博主无限进步",addFriendPlaceholder:"昵称（请勿包含博客等字样）：\n网站地址（要求博客地址，请勿提交个人主页）：\n头像图片url（请提供尽可能清晰的图片，我会上传到我自己的图床）：\n描述：\n站点截图（可选）：\n"},peoplecanvas:void 0,postHeadAiDescription:{enable:!0,gptName:"云AI",mode:"tianli",switchBtn:!1,btnLink:"https://afdian.net/item/886a79d4db6711eda42a52540025c377",randomNum:3,basicWordCount:1e3,key:"48580d1e3f53ae174a1e",Referer:"https://blog.tryrun.top"},diytitle:{enable:!0,leaveTitle:"w(ﾟДﾟ)w 不要走！再看看嘛！",backTitle:"♪(^∇^*)欢迎肥来！"},LA51:{enable:!0,ck:"JiFOrFoQklEn9YLS",LingQueMonitorID:"JiqlTmdeI4e1fPbd"},greetingBox:{enable:!0,default:"晚上好👋",list:[{greeting:"晚安😴",startTime:0,endTime:5},{greeting:"早上好鸭👋, 祝你一天好心情！",startTime:6,endTime:9},{greeting:"上午好👋, 状态很好，鼓励一下～",startTime:10,endTime:10},{greeting:"11点多啦, 在坚持一下就吃饭啦～",startTime:11,endTime:11},{greeting:"午安👋, 宝贝",startTime:12,endTime:14},{greeting:"🌈充实的一天辛苦啦！",startTime:14,endTime:18},{greeting:"19点喽, 奖励一顿丰盛的大餐吧🍔。",startTime:19,endTime:19},{greeting:"晚上好👋, 在属于自己的时间好好放松😌~",startTime:20,endTime:24}]},twikooEnvId:"https://twikoo.tryrun.top/",commentBarrageConfig:void 0,root:"/",preloader:{source:2},friends_vue_info:{apiurl:"https://friends.tryrun.top/"},navMusic:!1,mainTone:void 0,authorStatus:{skills:["🤖️ 数码科技爱好者","🔍 分享与热心帮助","🏠 智能家居小能手","🔨 设计开发一条龙","🤝 专修交互与设计","🏃 脚踏实地行动派","🧱 团队小组发动机","💢 壮汉人狠话不多"]},algolia:{appId:"T5VW6VDYLS",apiKey:"227bcb041816af13cb1698db15a8ac89",indexName:"hexo-blog",hits:{per_page:6},languages:{input_placeholder:"输入关键词后按下回车查找",hits_empty:"找不到您查询的内容：${query}",hits_stats:"找到 ${hits} 条结果，用时 ${time} 毫秒"}},localSearch:void 0,translate:{defaultEncoding:2,translateDelay:0,msgToTraditionalChinese:"繁",msgToSimplifiedChinese:"简",rightMenuMsgToTraditionalChinese:"转为繁体",rightMenuMsgToSimplifiedChinese:"转为简体"},noticeOutdate:{limitDay:365,position:"top",messagePrev:"It has been",messageNext:"days since the last update, the content of the article may be outdated."},highlight:{plugin:"highlighjs",highlightCopy:!0,highlightLang:!0,highlightHeightLimit:330},copy:{success:"复制成功",error:"复制错误",noSupport:"浏览器不支持"},relativeDate:{homepage:!0,simplehomepage:!1,post:!0},runtime:"天",date_suffix:{just:"刚刚",min:"分钟前",hour:"小时前",day:"天前",month:"个月前"},copyright:void 0,lightbox:"fancybox",Snackbar:{chs_to_cht:"你已切换为繁体",cht_to_chs:"你已切换为简体",day_to_night:"你已切换为深色模式",night_to_day:"你已切换为浅色模式",bgLight:"#425AEF",bgDark:"#1f1f1f",position:"top-center"},source:{justifiedGallery:{js:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.min.js",css:"https://cdn.cbd.int/flickr-justified-gallery@2.1.2/dist/fjGallery.css"}},isPhotoFigcaption:!1,islazyload:!0,isAnchor:!1,shortcutKey:void 0,autoDarkmode:!0}</script><script id="config-diff">var GLOBAL_CONFIG_SITE={configTitle:"云少IT",title:"Dubbo 源码分析 – 服务引用",postAI:"true",pageFillDescription:"四、Dubbo 源码分析 – 服务引用, 1. 简介, 2.服务引用原理, 3.源码分析, 3.1 处理配置, 3.2 引用服务, 3.2.1 创建 Invoker, 3.2.2 创建代理, 4.总结四源码分析服务引用简介在上一篇文章中我详细的分析了服务导出的原理本篇文章我们趁热打铁继续分析服务引用的原理在中我们可以通过两种方式引用远程服务第一种是使用服务直联的方式引用服务第二种方式是基于注册中心进行引用服务直联的方式仅适合在调试或测试服务的场景下使用不适合在线上环境使用因此本文我将重点分析通过注册中心引用服务的过程从注册中心中获取服务配置只是服务引用过程中的一环除此之外服务消费者还需要经历创建代理类创建等步骤这些步骤我将在后续章节中一一进行分析服务引用原理服务引用的时机有两个第一个是在容器调用的方法时引用服务第二个是在对应的服务被注入到其他类中时引用这两个引用服务的时机区别在于第一个是饿汉式的第二个是懒汉式的默认情况下使用懒汉式引用服务如果需要使用饿汉式可通过配置的属性开启下面我们按照默认配置进行分析整个分析过程从的方法开始当我们的服务被注入到其他类中时会第一时间调用方法并由该方法执行服务引用逻辑按照惯例在进行具体工作之前需先进行配置检查与收集工作接着根据收集到的信息决定服务用的方式有三种第一种是引用本地服务第二是通过直联方式引用远程服务第三是通过注册中心引用远程服务不管是哪种引用方式最后都会得到一个实例如果有多个注册中心多个服务提供者这个时候会得到一组实例此时需要通过集群管理类将多个合并成一个实例合并后的实例已经具备调用本地或远程服务的能力了但并不能将此实例暴露给用户使用这会对用户业务代码造成侵入此时框架还需要通过代理工厂类为服务接口生成代理类并让代理类去调用逻辑避免了框架代码对业务代码的侵入同时也让框架更容易使用以上就是引用服务的大致原理下面我们深入到代码中详细分析服务引用细节源码分析服务引用的入口方法为的方法该方法定义在的接口中实现了这个方法实现代码如下检测是否为空为空则通过方法创建方法主要用于处理配置以及调用生成代理类这里两个方法代码都比较简短并不难理解不过这里需要特别说明一下如果大家从方法进行代码调试时会碰到比较诧异的问题这里假设你使用且保持了的默认配置当你面调试到方法的时你会惊奇的发现不为空导致你无法进入到方法中继续调试导致这个现象的原因是框架本身有点小问题这个小问题会引发一些让人诧异的现象关于这个问题我进行了将近两个小时的排查查明问题后我给提交了一个修复了此问题另外前辈开了一个介绍这个问题有兴趣的朋友可以去看看大家如果想规避这个问题可以修改一下的配置在配置面板中搜索然后取消前的对号讲完需要注意的点我们继续向下分析接下来将分析配置的处理过程处理配置提供了丰富的配置用于调整和优化框架行为性能等在引用或导出服务时首先会对这些配置进行检查和处理以保证配置到正确性如果大家不是很熟悉配置建议先阅读以下官方文档配置解析的方法为的方法下面来看一下方法逻辑检测变量是否为空为空则创建设置检测是否为泛化接口加载类分割线从系统变量中获取与接口名对应的属性值从系统属性中获取解析文件路径从指定位置加载配置文件获取文件绝对路径从文件中加载配置获取与接口名对应的配置将赋值给分割线从中获取实例下同检测本地和本地存根配置合法性分割线添加协议版本信息时间戳和进程号等信息到中非泛化服务获取版本获取接口方法列表并添加到中将等对象的字段信息添加到中分割线遍历列表检测是否包含添加重试次数配置添加中的属性字段到比如等分割线获取服务消费者地址存储到系统上下文中创建代理类根据服务名代理类构建并将存入到中上面的代码很长做的事情比较多这里我根据代码逻辑对代码进行了分块下面我们一起来看一下首先是方法开始到分割线之间的代码这段代码主要用于检测实例是否存在如不存在则创建一个新的实例然后通过系统变量或配置文件填充的字段接着是检测泛化配置并根据配置设置的值本段代码逻辑大致就是这些接着来看分割线到分割线之间的逻辑这段逻辑用于从系统属性或配置文件中加载与接口名相对应的配置并将解析结果赋值给字段字段的作用一般是用于点对点调用继续向下看分割线和分割线之间的代码用于检测几个核心配置类是否为空为空则尝试从其他配置类中获取分割线与分割线之间的代码主要是用于收集各种配置并将配置存储到中分割线和分割线之间的代码用于处理实例该实例包含了事件通知配置比如等分割线到方法结尾的代码主要用于解析服务消费者以及调用创建代理对象关于该方法的详细分析将会在接下来的章节中展开到这里关于配置的检查与处理过长就分析完了这部分逻辑不是很难理解但比较繁杂大家需要耐心看一下好了本节先到这接下来分析服务引用的过程引用服务本节我们要从开始看起这个方法表面上看起来只是用于创建代理对象但实际上并非如此该方法还会调用其他方法构建以及合并实例具体细节如下配置被指定则不做本地引用根据的协议以及等参数检测是否需要本地引用比如如果用户显式配置了此时返回获取配置值本地引用生成本地引用协议为调用方法构建实例远程引用不为空表明用户可能想进行点对点调用当需要配置多个时可用分号进行分割这里会进行切分设置接口全限定名为路径检测协议是否为若是表明用户想使用指定的注册中心将转换为查询字符串并作为参数的值添加到中合并移除服务提供者的一些配置这些配置来源于用户配置的属性比如线程池相关配置并保留服务提供者的部分配置比如版本时间戳等最后将合并后的配置设置为查询字符串中加载注册中心添加参数到中并将添加到中未配置注册中心抛出异常单个注册中心或服务提供者服务直联下同调用的构建实例多个注册中心或多个服务提供者或者两者混合获取所有的通过调用构建会在运行时根据协议头加载指定的实例并调用实例的方法如果注册中心链接不为空则将使用创建实例并由对多个进行合并可用性检查生成代理类上面代码很多不过逻辑比较清晰首先根据配置检查是否为本地调用若是则调用的方法生成实例若不是则读取直联配置项或注册中心并将读取到的存储到中然后根据元素数量进行后续操作若元素数量为则直接通过自适应拓展构建实例接口若元素数量大于即存在多个注册中心或服务直联此时先根据构建然后再通过合并多个最后调用生成代理类这里的构建过程以及代理类的过程比较重要因此我将分两小节对这两个过程进行分析创建是的核心模型代表一个可执行体在服务提供方用于调用服务提供类在服务消费方用于执行远程调用在中的位置十分重要因此我们有必要去搞懂它从前面的代码中可知是由实现类构建的实现类有很多这里我会分析最常用的两个分别是和其他的大家自行分析下面先来分析的方法源码如下创建上面方法看起来比较简单不过这里有一个调用需要我们注意一下即这个方法用于获取客户端实例实例类型为实际上并不具备通信能力因此它需要更底层的客户端实例进行通信比如等默认情况下使用进行通信接下来我们简单看一下方法的逻辑是否共享连接获取连接数默认为表示未配置如果未配置则共享连接获取共享客户端初始化新的客户端这里根据数量决定是获取共享客户端还是创建新的客户端实例默认情况下使用共享客户端实例不过方法中也会调用方法因此下面我们一起看一下这两个方法获取带有引用计数功能的增加引用计数创建客户端将实例传给这里明显用了装饰模式上面方法先访问缓存若缓存未命中则通过方法创建新的实例并将该实例传给构造方法创建一个带有引用技术功能的实例内部实现比较简单就不分析了下面我们再来看一下方法的代码获取客户端类型默认为添加编解码和心跳包参数到中检测客户端类型是否存在不存在则抛出异常获取配置并根据配置值决定创建的客户端类型创建懒加载实例创建普通实例方法首先获取用户配置的客户端类型默认为然后检测用户配置的客户端类型是否存在不存在则抛出异常最后根据配置决定创建什么类型的客户端这里的代码并不是很复杂该类会在方法被调用时通过的方法创建客户端这里就不分析的代码了下面我们分析一下的方法获取实例默认为如上会通过加载实例这个方法比较简单大家自己看一下吧接下来分析的实现这里包含了多个调用分别如下创建对象创建对象通过构建实例创建对象这里的调用比较多我们这里重点看一下的方法如下如果数量大于则创建一个分发器获取自适应拓展类并调用方法生成实例这里方法返回的是自适应拓展类该类会在运行时根据客户端类型加载指定的实现类若用户未显示配置客户端类型则默认加载并调用该类的方法如下创建对象到这里就不继续跟下去了在往下就是通过提供的接口构建客户端了大家有兴趣自己看看到这里关于的方法就分析完了接下来继续分析的方法逻辑取参数值并将其设置为协议头获取注册中心实例这个判断暂时不知道有什么意图为什么要给类型生成将查询字符串转为获取配置通过加载实例并调用继续执行引用服务逻辑调用继续执行引用服务逻辑上面代码首先为设置协议头然后根据参数加载注册中心实例接下来对继续针对性处理这个处理逻辑我不是很明白不知道为什么要为类型生成有知道同学麻烦告知一下然后就是获取配置根据配置决定第一个参数的类型这里的重点是方法如下创建实例设置注册中心和协议生成服务消费者链接注册服务消费者在目录下新节点订阅等节点数据一个注册中心可能有多个服务提供者因此这里需要将多个服务提供者合并为一个如上方法创建一个实例然后生成服务者消费者链接并向注册中心进行注册注册完毕后紧接着订阅等节点下的数据完成订阅后会收到这几个节点下的子节点信息比如可以获取到服务提供者的配置信息由于一个服务可能部署在多台服务器上这样就会在产生多个节点这个时候就需要将多个服务节点合并为一个并生成一个关于和本文不打算进行分析相关分析将会在随后的文章中展开好了关于的创建的逻辑就先分析到这逻辑比较多大家耐心看一下创建代理创建完毕后接下来要做的事情是为服务接口生成代理对象有了代理对象我们就可以通过代理对象进行远程调用代理对象生成的入口方法为在的接下来进行分析调用重载方法获取接口列表切分接口列表设置服务接口类和到中加载接口类为和协议提供泛化调用支持参考创建新的数组设置到数组中调用重载方法如上上面大段代码都是用来获取数组的因此我们需要继续往下看这个方法是一个抽象方法下面我们到类中看一下该方法的实现代码生成子类是抽象类并调用子类的方法生成实例上面代码并不多首先是通过的方法获取子类然后创建对象并将该对象传给生成实例实现自的接口具体的用途是拦截接口类调用该类逻辑比较简单这里就不分析了下面我们重点关注一下的方法如下调用重载方法遍历接口列表检测类型是否为接口重新加载接口类检测接口是否相同这里有可能为空拼接接口全限定名分隔符为使用拼接后接口名作为从缓存中获取实例多线程控制保证只有一个线程可以进行后续操作其他线程在此处进行等待放置标志位到缓存中并跳出循环进行后续操作创建对象检测接口访问级别是否为或获取接口包名非级别的接口必须在同一个包下否者抛出异常添加接口到中遍历接口方法获取方法描述可理解为方法签名如果已包含在中则忽略考虑这种情况接口和接口中包含一个完全相同的方法获取方法返回值类型获取参数列表生成生成生成接口的方法调用语句如下返回值不为生成返回语句形如添加方法名访问控制符参数列表方法代码等信息到中构建接口代理类名称比如生成为接口代理类添加带有参数的构造方法比如为接口代理类添加默认构造方法生成接口代理类构建子类名称比如等为的抽象方法生成实现代码形如生成实现类通过反射创建实例释放资源写缓存唤醒其他等待线程上面代码比较复杂我也写了很多注释大家在阅读这段代码时要搞清楚和的用途不然会被搞晕用于为服务接口生成代理类比如我们有一个接口这个接口代理类就是由生成的则是用于为抽象类生成子类主要是实现的抽象方法下面以这个接口为例来看一下该接口代理类代码大致是怎样的忽略接口好了到这里代理类生成逻辑就分析完了整个过程比较复杂大家需要耐心看一下本节点到这里总结本篇文章对服务引用的过程进行了较为详尽的分析之所以说是较为详尽是因为还有一些地方没有分析到比如等实现类的代码并未进行详细分析由于这些类功能比较独立因此我打算后续单独成文进行分析暂时我们可以先把这些类看成黑盒只要知道这些类的用途即可引用服务过程涉及到的调用也非常多大家在阅读相关代码的中耐心些并多进行调试好了本篇文章就先到这里了谢谢阅读",isPost:!0,isHome:!1,isHighlightShrink:!0,isToc:!0,postUpdate:"2020-09-22 22:29:54",postMainColor:""}</script><noscript><style>#nav{opacity:1}.justified-gallery img{opacity:1}#post-meta time,#recent-posts time{display:inline!important}</style></noscript><script>(e=>{e.saveToLocal={set:(e,t,a)=>{var o;0!==a&&(o=Date.now(),localStorage.setItem(e,JSON.stringify({value:t,expiry:o+864e5*a})))},get:e=>{var t=localStorage.getItem(e);if(t){t=JSON.parse(t);if(!(Date.now()>t.expiry))return t.value;localStorage.removeItem(e)}}},e.getScript=(o,c={})=>new Promise((t,e)=>{const a=document.createElement("script");a.src=o,a.async=!0,a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},Object.keys(c).forEach(e=>{a.setAttribute(e,c[e])}),document.head.appendChild(a)}),e.getCSS=(o,c=!1)=>new Promise((t,e)=>{const a=document.createElement("link");a.rel="stylesheet",a.href=o,c&&(a.id=c),a.onerror=e,a.onload=a.onreadystatechange=function(){var e=this.readyState;e&&"loaded"!==e&&"complete"!==e||(a.onload=a.onreadystatechange=null,t())},document.head.appendChild(a)}),e.activateDarkMode=()=>{document.documentElement.setAttribute("data-theme","dark"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#0d0d0d")},e.activateLightMode=()=>{document.documentElement.setAttribute("data-theme","light"),null!==document.querySelector('meta[name="theme-color"]')&&document.querySelector('meta[name="theme-color"]').setAttribute("content","#ffffff")};var e=saveToLocal.get("theme"),t=window.matchMedia("(prefers-color-scheme: dark)").matches,a=window.matchMedia("(prefers-color-scheme: light)").matches,o=window.matchMedia("(prefers-color-scheme: no-preference)").matches,c=!t&&!a&&!o,t=(void 0===e?(a?activateLightMode():t?activateDarkMode():(o||c)&&((a=(new Date).getHours())<=6||18<=a?activateDarkMode:activateLightMode)(),window.matchMedia("(prefers-color-scheme: dark)").addListener(e=>{void 0===saveToLocal.get("theme")&&(e.matches?activateDarkMode:activateLightMode)()})):("light"===e?activateLightMode:activateDarkMode)(),saveToLocal.get("aside-status"));void 0!==t&&("hide"===t?document.documentElement.classList.add("hide-aside"):document.documentElement.classList.remove("hide-aside"));/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)&&document.documentElement.classList.add("apple")})(window)</script><link rel="stylesheet" href="/css/1.min.css?1" media="async" onload='this.media="all"'><link rel="stylesheet" href="/css/bg.css?1" media="async" onload='this.media="all"'><meta name="generator" content="Hexo 6.3.0"><link rel="alternate" href="/atom.xml" title="云少IT" type="application/atom+xml"><link rel="alternate" href="/rss.xml" title="云少IT" type="application/rss+xml"></head><body data-type="anzhiyu"><div id="web_bg"></div><div id="an_music_bg"></div><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.10/progress_bar/progress_bar.css"><script async src="https://cdn.cbd.int/pace-js@1.2.4/pace.min.js" data-pace-options="{ &quot;restartOnRequestAfter&quot;:false,&quot;eventLag&quot;:false}"></script><div class="post" id="body-wrap"><header class="post-bg" id="page-header"><nav id="nav"><div id="nav-group"><span id="blog_name"><div class="back-home-button"><i class="anzhiyufont anzhiyu-icon-grip-vertical"></i><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div></div><a id="site-name" href="/" accesskey="h"><div class="title">云少IT</div><i class="anzhiyufont anzhiyu-icon-house-chimney"></i></a></span><div class="mask-name-container"><div id="name-container"><a id="page-name" href="javascript:anzhiyu.scrollToDest(0, 500)" rel="external nofollow noreferrer">PAGE_NAME</a></div></div><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div></div><div id="nav-right"><div class="nav-button only-home" id="travellings_button" title="随机前往一个开往项目网站"><a class="site-page" onclick="anzhiyu.totraveling()" title="随机前往一个开往项目网站" href="javascript:void(0);" rel="external nofollow" data-pjax-state="external"><i class="anzhiyufont anzhiyu-icon-train"></i></a></div><div class="nav-button" id="randomPost_button"><a class="site-page" onclick="toRandomPost()" title="随机前往一个文章" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-dice"></i></a></div><div class="nav-button" id="search-button"><a class="site-page social-icon search" href="javascript:void(0);" rel="external nofollow noreferrer" title="搜索🔍" accesskey="s"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span> 搜索</span></a></div><input id="center-console" type="checkbox"><label class="widget" for="center-console" title="中控台" onclick="anzhiyu.switchConsole()"><i class="left"></i><i class="widget center"></i><i class="widget right"></i></label><div id="console"><div class="console-card-group-reward"><ul class="reward-all console-card"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" alt="wechat" src="/img/wxpay.webp"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" alt="alipay" src="/img/alipay.webp"></a><div class="post-qr-code-desc">alipay</div></li></ul></div><div class="console-card-group"><div class="console-card-group-left"><div class="console-card" id="card-newest-comments"><div class="card-content"><div class="author-content-item-tips">互动</div><span class="author-content-item-title">最新评论</span></div><div class="aside-list"><span>正在加载中...</span></div></div></div><div class="console-card-group-right"><div class="console-card tags"><div class="card-content"><div class="author-content-item-tips">兴趣点</div><span class="author-content-item-title">寻找你感兴趣的领域</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:1.05rem;color:#637571">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:1.05rem;color:#8a0460">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:1.05rem;color:#6b3641">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:1.05rem;color:#5e6603">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:1.05rem;color:#abb61f">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:1.05rem;color:#6dc55c">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:1.05rem;color:#8745c2">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:1.05rem;color:#804042">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:1.05rem;color:#21b4c6">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:1.05rem;color:#0a3988">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:1.05rem;color:#29446d">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:1.05rem;color:#afc378">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:1.05rem;color:#b10843">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:1.05rem;color:#9663a4">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:1.05rem;color:#b4214d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:1.05rem;color:#220f01">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:1.05rem;color:#c4106c">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:1.05rem;color:#1f5f9f">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:1.05rem;color:#c3563b">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:1.05rem;color:#1a3364">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:1.05rem;color:#1ea132">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:1.05rem;color:#a00679">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:1.05rem;color:#9e5e9b">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:1.05rem;color:#4d47bc">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:1.05rem;color:#a56245">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:1.05rem;color:#9c8435">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:1.05rem;color:#647e28">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:1.05rem;color:#a192b9">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:1.05rem;color:#22a370">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:1.05rem;color:#6b985c">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:1.05rem;color:#bc5f40">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:1.05rem;color:#167366">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:1.05rem;color:#aa928f">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:1.05rem;color:#3a740b">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:1.05rem;color:#75c343">面试<sup>11</sup></a></div></div><hr></div></div><div class="console-card history"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-box-archiv"></i><span>文章</span></div><div class="card-archives"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-archive"></i><span>归档</span><a class="card-more-btn" href="/archives/" title="查看更多"> <i class="anzhiyufont anzhiyu-icon-angle-right"></i></a></div><ul class="card-archive-list"><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/06/"><span class="card-archive-list-date">六月 2023</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2021/05/"><span class="card-archive-list-date">五月 2021</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">2</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/10/"><span class="card-archive-list-date">十月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">21</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/09/"><span class="card-archive-list-date">九月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">44</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/08/"><span class="card-archive-list-date">八月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">47</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/07/"><span class="card-archive-list-date">七月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">42</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/06/"><span class="card-archive-list-date">六月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">31</span><span>篇</span></div></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2020/05/"><span class="card-archive-list-date">五月 2020</span><div class="card-archive-list-count-group"><span class="card-archive-list-count">43</span><span>篇</span></div></a></li></ul></div><hr></div></div></div><div class="button-group"><div class="console-btn-item"><a class="darkmode_switchbutton" title="显示模式切换" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-moon"></i></a></div><div class="console-btn-item" id="consoleHideAside" onclick="anzhiyu.hideAsideBtn()" title="边栏显示控制"><a class="asideSwitch"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></a></div><div class="console-btn-item on" id="consoleCommentBarrage" onclick="anzhiyu.switchCommentBarrage()" title="热评开关"><a class="commentBarrage"><i class="anzhiyufont anzhiyu-icon-message"></i></a></div><div class="console-btn-item" id="consoleMusic" onclick="anzhiyu.musicToggle()" title="音乐开关"><a class="music-switch"><i class="anzhiyufont anzhiyu-icon-music"></i></a></div></div><div class="console-mask" onclick="anzhiyu.hideConsole()" href="javascript:void(0);" rel="external nofollow noreferrer"></div></div><div class="nav-button" id="nav-totop"><a class="totopbtn" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i><span id="percent" onclick="anzhiyu.scrollToDest(0,500)">0</span></a></div><div id="toggle-menu"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer" title="切换"><i class="anzhiyufont anzhiyu-icon-bars"></i></a></div></div></div></nav><div id="post-info"><div id="post-firstinfo"><div class="meta-firstline"><a class="post-meta-original">原创</a><span class="article-meta tags"></span></div></div><h1 class="post-title" itemprop="name headline">Dubbo 源码分析 – 服务引用</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="anzhiyufont anzhiyu-icon-calendar-days post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" itemprop="dateCreated datePublished" datetime="2020-09-22T14:29:54.000Z" title="发表于 2020-09-22 22:29:54">2020-09-22</time><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-history post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" itemprop="dateCreated datePublished" datetime="2020-09-22T14:29:54.000Z" title="更新于 2020-09-22 22:29:54">2020-09-22</time></span></div><div class="meta-secondline"><span class="post-meta-separator"></span><span class="post-meta-wordcount"><i class="anzhiyufont anzhiyu-icon-file-word post-meta-icon" title="文章字数"></i><span class="post-meta-label" title="文章字数">字数总计:</span><span class="word-count" title="文章字数">7.9k</span><span class="post-meta-separator"></span><i class="anzhiyufont anzhiyu-icon-clock post-meta-icon" title="阅读时长"></i><span class="post-meta-label" title="阅读时长">阅读时长:</span><span>32分钟</span></span><span class="post-meta-separator"></span><span class="post-meta-pv-cv" data-flag-title="Dubbo 源码分析 – 服务引用"><i class="anzhiyufont anzhiyu-icon-fw-eye post-meta-icon"></i><span class="post-meta-label" title="阅读量">阅读量:</span><span id="busuanzi_value_page_pv"><i class="anzhiyufont anzhiyu-icon-spinner anzhiyu-spin"></i></span></span><span class="post-meta-separator"> </span><span class="post-meta-position" title="作者IP属地为武汉"><i class="anzhiyufont anzhiyu-icon-location-dot"></i>武汉</span></div></div></div><section class="main-hero-waves-area waves-area"><svg class="waves-svg" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"><defs><path id="gentle-wave" d="M -160 44 c 30 0 58 -18 88 -18 s 58 18 88 18 s 58 -18 88 -18 s 58 18 88 18 v 44 h -352 Z"></path></defs><g class="parallax"><use href="#gentle-wave" x="48" y="0"></use><use href="#gentle-wave" x="48" y="3"></use><use href="#gentle-wave" x="48" y="5"></use><use href="#gentle-wave" x="48" y="7"></use></g></svg></section><div id="post-top-cover"><img class="nolazyload" id="post-top-bg" src="https://www.bing.com/th?id=OHR.HedgehogNest_EN-GB2384367951_UHD.jpg"></div></header><main id="blog-container"><div class="layout" id="content-inner"><div id="post"><div class="post-ai-description"><div class="ai-title"><i class="anzhiyufont anzhiyu-icon-bilibili"></i><div class="ai-title-text">AI-摘要</div><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i><i class="anzhiyufont anzhiyu-icon-circle-dot" title="朗读摘要"></i><div id="ai-tag">Tianli GPT</div></div><div class="ai-explanation">AI初始化中...</div><div class="ai-btn-box"><div class="ai-btn-item">介绍自己 🙈</div><div class="ai-btn-item">生成本文简介 👋</div><div class="ai-btn-item">推荐相关文章 📖</div><div class="ai-btn-item">前往主页 🏠</div><div class="ai-btn-item" id="go-tianli-blog">前往爱发电购买</div></div><script data-pjax src="/js/anzhiyu/ai_abstract.js"></script></div><article class="post-content" id="article-container" itemscope itemtype="https://it985.github.io/posts/d72547db.html"><header><h1 id="CrawlerTitle" itemprop="name headline">Dubbo 源码分析 – 服务引用</h1><span itemprop="author" itemscope itemtype="http://schema.org/Person">云少</span><time itemprop="dateCreated datePublished" datetime="2020-09-22T14:29:54.000Z" title="发表于 2020-09-22 22:29:54">2020-09-22</time><time itemprop="dateCreated datePublished" datetime="2020-09-22T14:29:54.000Z" title="更新于 2020-09-22 22:29:54">2020-09-22</time></header><h1 id="四、Dubbo-源码分析-–-服务引用"><a href="#四、Dubbo-源码分析-–-服务引用" class="headerlink" title="四、Dubbo 源码分析 – 服务引用"></a>四、Dubbo 源码分析 – 服务引用</h1><h2 id="1-简介"><a href="#1-简介" class="headerlink" title="1. 简介"></a>1. 简介</h2><p>在<a href="">上一篇</a>文章中，我详细的分析了服务导出的原理。本篇文章我们趁热打铁，继续分析服务引用的原理。在 Dubbo 中，我们可以通过两种方式引用远程服务。第一种是使用服务直联的方式引用服务，第二种方式是基于注册中心进行引用。服务直联的方式仅适合在调试或测试服务的场景下使用，不适合在线上环境使用。因此，本文我将重点分析通过注册中心引用服务的过程。从注册中心中获取服务配置只是服务引用过程中的一环，除此之外，服务消费者还需要经历 Invoker 创建、代理类创建等步骤。这些步骤，我将在后续章节中一一进行分析。</p><h2 id="2-服务引用原理"><a href="#2-服务引用原理" class="headerlink" title="2.服务引用原理"></a>2.服务引用原理</h2><p>Dubbo 服务引用的时机有两个，第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务，第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于，第一个是饿汉式的，第二个是懒汉式的。默认情况下，Dubbo 使用懒汉式引用服务。如果需要使用饿汉式，可通过配置 的 init 属性开启。下面我们按照 Dubbo 默认配置进行分析，整个分析过程从 ReferenceBean 的 getObject 方法开始。当我们的服务被注入到其他类中时，Spring 会第一时间调用 getObject 方法，并由该方法执行服务引用逻辑。按照惯例，在进行具体工作之前，需先进行配置检查与收集工作。接着根据收集到的信息决定服务用的方式，有三种，第一种是引用本地 (JVM) 服务，第二是通过直联方式引用远程服务，第三是通过注册中心引用远程服务。不管是哪种引用方式，最后都会得到一个 Invoker 实例。如果有多个注册中心，多个服务提供者，这个时候会得到一组 Invoker 实例，此时需要通过集群管理类 Cluster 将多个 Invoker 合并成一个实例。合并后的 Invoker 实例已经具备调用本地或远程服务的能力了，但并不能将此实例暴露给用户使用，这会对用户业务代码造成侵入。此时框架还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类，并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入，同时也让框架更容易使用。</p><p>以上就是 Dubbo 引用服务的大致原理，下面我们深入到代码中，详细分析服务引用细节。</p><h2 id="3-源码分析"><a href="#3-源码分析" class="headerlink" title="3.源码分析"></a>3.源码分析</h2><p>服务引用的入口方法为 ReferenceBean 的 getObject 方法，该方法定义在 Spring 的 FactoryBean 接口中，ReferenceBean 实现了这个方法。实现代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Object <span class="title function_">getObject</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">    <span class="keyword">return</span> get();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> T <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (destroyed) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Already destroyed!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 检测 ref 是否为空，为空则通过 init 方法创建</span></span><br><span class="line">    <span class="keyword">if</span> (ref == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// init 方法主要用于处理配置，以及调用 createProxy 生成代理类</span></span><br><span class="line">        init();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> ref;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里两个方法代码都比较简短，并不难理解。不过这里需要特别说明一下，如果大家从 getObject 方法进行代码调试时，会碰到比较诧异的问题。这里假设你使用 IDEA，且保持了 IDEA 的默认配置。当你面调试到 get 方法的<code>if (ref == null)</code>时，你会惊奇的发现 ref 不为空，导致你无法进入到 init 方法中继续调试。导致这个现象的原因是 Dubbo 框架本身有点小问题，这个小问题会引发一些让人诧异的现象。关于这个问题，我进行了将近两个小时的排查。查明问题后，我给 Dubbo 提交了一个 pull request (<a target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/apache/incubator-dubbo/pull/2754">#2754</a>) 修复了此问题。另外，beiwei30 前辈开了一个 issue (<a target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/apache/incubator-dubbo/issues/2757">#2757</a> ) 介绍这个问题，有兴趣的朋友可以去看看。大家如果想规避这个问题，可以修改一下 IDEA 的配置。在配置面板中搜索 toString，然后取消<code>Enable &#39;toString&#39; object view</code>前的对号。</p><p>讲完需要注意的点，我们继续向下分析，接下来将分析配置的处理过程。</p><h3 id="3-1-处理配置"><a href="#3-1-处理配置" class="headerlink" title="3.1 处理配置"></a>3.1 处理配置</h3><p>Dubbo 提供了丰富的配置，用于调整和优化框架行为，性能等。Dubbo 在引用或导出服务时，首先会对这些配置进行检查和处理，以保证配置到正确性。如果大家不是很熟悉 Dubbo 配置，建议先阅读以下官方文档。配置解析的方法为 ReferenceConfig 的 init 方法，下面来看一下方法逻辑。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (initialized) &#123;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    initialized = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">if</span> (interfaceName == <span class="literal">null</span> || interfaceName.length() == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;interface not allow null!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检测 consumer 变量是否为空，为空则创建</span></span><br><span class="line">    checkDefault();</span><br><span class="line">    appendProperties(<span class="built_in">this</span>);</span><br><span class="line">    <span class="keyword">if</span> (getGeneric() == <span class="literal">null</span> &amp;&amp; getConsumer() != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 设置 generic</span></span><br><span class="line">        setGeneric(getConsumer().getGeneric());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检测是否为泛化接口</span></span><br><span class="line">    <span class="keyword">if</span> (ProtocolUtils.isGeneric(getGeneric())) &#123;</span><br><span class="line">        interfaceClass = GenericService.class;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 加载类</span></span><br><span class="line">            interfaceClass = Class.forName(interfaceName, <span class="literal">true</span>, Thread.currentThread()</span><br><span class="line">                    .getContextClassLoader());</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(e.getMessage(), e);</span><br><span class="line">        &#125;</span><br><span class="line">        checkInterfaceAndMethods(interfaceClass, methods);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// -------------------------------✨ 分割线1 ✨------------------------------</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 从系统变量中获取与接口名对应的属性值</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">resolve</span> <span class="operator">=</span> System.getProperty(interfaceName);</span><br><span class="line">    <span class="type">String</span> <span class="variable">resolveFile</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">if</span> (resolve == <span class="literal">null</span> || resolve.length() == <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 从系统属性中获取解析文件路径</span></span><br><span class="line">        resolveFile = System.getProperty(<span class="string">&quot;dubbo.resolve.file&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (resolveFile == <span class="literal">null</span> || resolveFile.length() == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 从指定位置加载配置文件</span></span><br><span class="line">            <span class="type">File</span> <span class="variable">userResolveFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="keyword">new</span> <span class="title class_">File</span>(System.getProperty(<span class="string">&quot;user.home&quot;</span>)), <span class="string">&quot;dubbo-resolve.properties&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span> (userResolveFile.exists()) &#123;</span><br><span class="line">                <span class="comment">// 获取文件绝对路径</span></span><br><span class="line">                resolveFile = userResolveFile.getAbsolutePath();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (resolveFile != <span class="literal">null</span> &amp;&amp; resolveFile.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">Properties</span> <span class="variable">properties</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Properties</span>();</span><br><span class="line">            <span class="type">FileInputStream</span> <span class="variable">fis</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                fis = <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(<span class="keyword">new</span> <span class="title class_">File</span>(resolveFile));</span><br><span class="line">                <span class="comment">// 从文件中加载配置</span></span><br><span class="line">                properties.load(fis);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Unload ..., cause:...&quot;</span>);</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span> (<span class="literal">null</span> != fis) fis.close();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">                    logger.warn(e.getMessage(), e);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 获取与接口名对应的配置</span></span><br><span class="line">            resolve = properties.getProperty(interfaceName);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (resolve != <span class="literal">null</span> &amp;&amp; resolve.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 将 resolve 赋值给 url</span></span><br><span class="line">        url = resolve;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// -------------------------------✨ 分割线2 ✨------------------------------</span></span><br><span class="line">    <span class="keyword">if</span> (consumer != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (application == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// 从 consumer 中获取 Application 实例，下同</span></span><br><span class="line">            application = consumer.getApplication();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">module</span> == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">module</span> = consumer.getModule();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (registries == <span class="literal">null</span>) &#123;</span><br><span class="line">            registries = consumer.getRegistries();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (monitor == <span class="literal">null</span>) &#123;</span><br><span class="line">            monitor = consumer.getMonitor();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">module</span> != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (registries == <span class="literal">null</span>) &#123;</span><br><span class="line">            registries = <span class="keyword">module</span>.getRegistries();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (monitor == <span class="literal">null</span>) &#123;</span><br><span class="line">            monitor = <span class="keyword">module</span>.getMonitor();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (application != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (registries == <span class="literal">null</span>) &#123;</span><br><span class="line">            registries = application.getRegistries();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (monitor == <span class="literal">null</span>) &#123;</span><br><span class="line">            monitor = application.getMonitor();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检测本地 Application 和本地存根配置合法性</span></span><br><span class="line">    checkApplication();</span><br><span class="line">    checkStubAndMock(interfaceClass);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// -------------------------------✨ 分割线3 ✨------------------------------</span></span><br><span class="line"></span><br><span class="line">    Map&lt;String, String&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, String&gt;();</span><br><span class="line">    Map&lt;Object, Object&gt; attributes = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;Object, Object&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加 side、协议版本信息、时间戳和进程号等信息到 map 中</span></span><br><span class="line">    map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);</span><br><span class="line">    map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());</span><br><span class="line">    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));</span><br><span class="line">    <span class="keyword">if</span> (ConfigUtils.getPid() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!isGeneric()) &#123;    <span class="comment">// 非泛化服务</span></span><br><span class="line">        <span class="comment">// 获取版本</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">revision</span> <span class="operator">=</span> Version.getVersion(interfaceClass, version);</span><br><span class="line">        <span class="keyword">if</span> (revision != <span class="literal">null</span> &amp;&amp; revision.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            map.put(<span class="string">&quot;revision&quot;</span>, revision);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 获取接口方法列表，并添加到 map 中</span></span><br><span class="line">        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();</span><br><span class="line">        <span class="keyword">if</span> (methods.length == <span class="number">0</span>) &#123;</span><br><span class="line">            map.put(<span class="string">&quot;methods&quot;</span>, Constants.ANY_VALUE);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            map.put(<span class="string">&quot;methods&quot;</span>, StringUtils.join(<span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;String&gt;(Arrays.asList(methods)), <span class="string">&quot;,&quot;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    map.put(Constants.INTERFACE_KEY, interfaceName);</span><br><span class="line">    <span class="comment">// 将 ApplicationConfig、ConsumerConfig、ReferenceConfig 等对象的字段信息添加到 map 中</span></span><br><span class="line">    appendParameters(map, application);</span><br><span class="line">    appendParameters(map, <span class="keyword">module</span>);</span><br><span class="line">    appendParameters(map, consumer, Constants.DEFAULT_KEY);</span><br><span class="line">    appendParameters(map, <span class="built_in">this</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// -------------------------------✨ 分割线4 ✨------------------------------</span></span><br><span class="line"></span><br><span class="line">    <span class="type">String</span> <span class="variable">prefix</span> <span class="operator">=</span> StringUtils.getServiceKey(map);</span><br><span class="line">    <span class="keyword">if</span> (methods != <span class="literal">null</span> &amp;&amp; !methods.isEmpty()) &#123;</span><br><span class="line">        <span class="comment">// 遍历 MethodConfig 列表</span></span><br><span class="line">        <span class="keyword">for</span> (MethodConfig method : methods) &#123;</span><br><span class="line">            appendParameters(map, method, method.getName());</span><br><span class="line">            <span class="type">String</span> <span class="variable">retryKey</span> <span class="operator">=</span> method.getName() + <span class="string">&quot;.retry&quot;</span>;</span><br><span class="line">            <span class="comment">// 检测 map 是否包含 methodName.retry</span></span><br><span class="line">            <span class="keyword">if</span> (map.containsKey(retryKey)) &#123;</span><br><span class="line">                <span class="type">String</span> <span class="variable">retryValue</span> <span class="operator">=</span> map.remove(retryKey);</span><br><span class="line">                <span class="keyword">if</span> (<span class="string">&quot;false&quot;</span>.equals(retryValue)) &#123;</span><br><span class="line">                    <span class="comment">// 添加重试次数配置 methodName.retries</span></span><br><span class="line">                    map.put(method.getName() + <span class="string">&quot;.retries&quot;</span>, <span class="string">&quot;0&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 添加 MethodConfig 中的“属性”字段到 attributes</span></span><br><span class="line">            <span class="comment">// 比如 onreturn、onthrow、oninvoke 等</span></span><br><span class="line">            appendAttributes(attributes, method, prefix + <span class="string">&quot;.&quot;</span> + method.getName());</span><br><span class="line">            checkAndConvertImplicitConfig(method, map, attributes);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// -------------------------------✨ 分割线5 ✨------------------------------</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取服务消费者 ip 地址</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">hostToRegistry</span> <span class="operator">=</span> ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);</span><br><span class="line">    <span class="keyword">if</span> (hostToRegistry == <span class="literal">null</span> || hostToRegistry.length() == <span class="number">0</span>) &#123;</span><br><span class="line">        hostToRegistry = NetUtils.getLocalHost();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (isInvalidLocalHost(hostToRegistry)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Specified invalid registry ip from property...&quot;</span> );</span><br><span class="line">    &#125;</span><br><span class="line">    map.put(Constants.REGISTER_IP_KEY, hostToRegistry);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 存储 attributes 到系统上下文中</span></span><br><span class="line">    StaticContext.getSystemContext().putAll(attributes);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 创建代理类</span></span><br><span class="line">    ref = createProxy(map);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 根据服务名，ReferenceConfig，代理类构建 ConsumerModel，</span></span><br><span class="line">    <span class="comment">// 并将 ConsumerModel 存入到 ApplicationModel 中</span></span><br><span class="line">    <span class="type">ConsumerModel</span> <span class="variable">consumerModel</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ConsumerModel</span>(getUniqueServiceName(), <span class="built_in">this</span>, ref, interfaceClass.getMethods());</span><br><span class="line">    ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面的代码很长，做的事情比较多。这里我根据代码逻辑，对代码进行了分块，下面我们一起来看一下。</p><p>首先是方法开始到分割线1之间的代码。这段代码主要用于检测 ConsumerConfig 实例是否存在，如不存在则创建一个新的实例，然后通过系统变量或 dubbo.properties 配置文件填充 ConsumerConfig 的字段。接着是检测泛化配置，并根据配置设置 interfaceClass 的值。本段代码逻辑大致就是这些，接着来看分割线1到分割线2之间的逻辑。这段逻辑用于从系统属性或配置文件中加载与接口名相对应的配置，并将解析结果赋值给 url 字段。url 字段的作用一般是用于点对点调用。继续向下看，分割线2和分割线3之间的代码用于检测几个核心配置类是否为空，为空则尝试从其他配置类中获取。分割线3与分割线4之间的代码主要是用于收集各种配置，并将配置存储到 map 中。分割线4和分割线5之间的代码用于处理 MethodConfig 实例。该实例包含了事件通知配置，比如 onreturn、onthrow、oninvoke 等。分割线5到方法结尾的代码主要用于解析服务消费者 ip，以及调用 createProxy 创建代理对象。关于该方法的详细分析，将会在接下来的章节中展开。</p><p>到这里，关于配置的检查与处理过长就分析完了。这部分逻辑不是很难理解，但比较繁杂，大家需要耐心看一下。好了，本节先到这，接下来分析服务引用的过程。</p><h3 id="3-2-引用服务"><a href="#3-2-引用服务" class="headerlink" title="3.2 引用服务"></a>3.2 引用服务</h3><p>本节我们要从 createProxy 开始看起。createProxy 这个方法表面上看起来只是用于创建代理对象，但实际上并非如此。该方法还会调用其他方法构建以及合并 Invoker 实例。具体细节如下。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> T <span class="title function_">createProxy</span><span class="params">(Map&lt;String, String&gt; map)</span> &#123;</span><br><span class="line">    <span class="type">URL</span> <span class="variable">tmpUrl</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">URL</span>(<span class="string">&quot;temp&quot;</span>, <span class="string">&quot;localhost&quot;</span>, <span class="number">0</span>, map);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">boolean</span> isJvmRefer;</span><br><span class="line">    <span class="keyword">if</span> (isInjvm() == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// url 配置被指定，则不做本地引用</span></span><br><span class="line">        <span class="keyword">if</span> (url != <span class="literal">null</span> &amp;&amp; url.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            isJvmRefer = <span class="literal">false</span>;</span><br><span class="line">        <span class="comment">// 根据 url 的协议、scope 以及 injvm 等参数检测是否需要本地引用</span></span><br><span class="line">        <span class="comment">// 比如如果用户显式配置了 scope=local，此时 isInjvmRefer 返回 true</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) &#123;</span><br><span class="line">            isJvmRefer = <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            isJvmRefer = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 获取 injvm 配置值</span></span><br><span class="line">        isJvmRefer = isInjvm().booleanValue();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 本地引用</span></span><br><span class="line">    <span class="keyword">if</span> (isJvmRefer) &#123;</span><br><span class="line">        <span class="comment">// 生成本地引用 URL，协议为 injvm</span></span><br><span class="line">        <span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">URL</span>(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, <span class="number">0</span>, interfaceClass.getName()).addParameters(map);</span><br><span class="line">        <span class="comment">// 调用 refer 方法构建 InjvmInvoker 实例</span></span><br><span class="line">        invoker = refprotocol.refer(interfaceClass, url);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 远程引用</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// url 不为空，表明用户可能想进行点对点调用</span></span><br><span class="line">        <span class="keyword">if</span> (url != <span class="literal">null</span> &amp;&amp; url.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="comment">// 当需要配置多个 url 时，可用分号进行分割，这里会进行切分</span></span><br><span class="line">            String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);</span><br><span class="line">            <span class="keyword">if</span> (us != <span class="literal">null</span> &amp;&amp; us.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">for</span> (String u : us) &#123;</span><br><span class="line">                    <span class="type">URL</span> <span class="variable">url</span> <span class="operator">=</span> URL.valueOf(u);</span><br><span class="line">                    <span class="keyword">if</span> (url.getPath() == <span class="literal">null</span> || url.getPath().length() == <span class="number">0</span>) &#123;</span><br><span class="line">                        <span class="comment">// 设置接口全限定名为 url 路径</span></span><br><span class="line">                        url = url.setPath(interfaceName);</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 检测 url 协议是否为 registry，若是，表明用户想使用指定的注册中心</span></span><br><span class="line">                    <span class="keyword">if</span> (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) &#123;</span><br><span class="line">                        <span class="comment">// 将 map 转换为查询字符串，并作为 refer 参数的值添加到 url 中</span></span><br><span class="line">                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="comment">// 合并 url，移除服务提供者的一些配置（这些配置来源于用户配置的 url 属性），</span></span><br><span class="line">                        <span class="comment">// 比如线程池相关配置。并保留服务提供者的部分配置，比如版本，group，时间戳等</span></span><br><span class="line">                        <span class="comment">// 最后将合并后的配置设置为 url 查询字符串中。</span></span><br><span class="line">                        urls.add(ClusterUtils.mergeUrl(url, map));</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 加载注册中心 url</span></span><br><span class="line">            List&lt;URL&gt; us = loadRegistries(<span class="literal">false</span>);</span><br><span class="line">            <span class="keyword">if</span> (us != <span class="literal">null</span> &amp;&amp; !us.isEmpty()) &#123;</span><br><span class="line">                <span class="keyword">for</span> (URL u : us) &#123;</span><br><span class="line">                    <span class="type">URL</span> <span class="variable">monitorUrl</span> <span class="operator">=</span> loadMonitor(u);</span><br><span class="line">                    <span class="keyword">if</span> (monitorUrl != <span class="literal">null</span>) &#123;</span><br><span class="line">                        map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="comment">// 添加 refer 参数到 url 中，并将 url 添加到 urls 中</span></span><br><span class="line">                    urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 未配置注册中心，抛出异常</span></span><br><span class="line">            <span class="keyword">if</span> (urls.isEmpty()) &#123;</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;No such any registry to reference...&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 单个注册中心或服务提供者(服务直联，下同)</span></span><br><span class="line">        <span class="keyword">if</span> (urls.size() == <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="comment">// 调用 RegistryProtocol 的 refer 构建 Invoker 实例</span></span><br><span class="line">            invoker = refprotocol.refer(interfaceClass, urls.get(<span class="number">0</span>));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 多个注册中心或多个服务提供者，或者两者混合</span></span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            List&lt;Invoker&lt;?&gt;&gt; invokers = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;Invoker&lt;?&gt;&gt;();</span><br><span class="line">            <span class="type">URL</span> <span class="variable">registryURL</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 获取所有的 Invoker</span></span><br><span class="line">            <span class="keyword">for</span> (URL url : urls) &#123;</span><br><span class="line">                <span class="comment">// 通过 refprotocol 调用 refer 构建 Invoker，refprotocol 会在运行时</span></span><br><span class="line">                <span class="comment">// 根据 url 协议头加载指定的 Protocol 实例，并调用实例的 refer 方法</span></span><br><span class="line">                invokers.add(refprotocol.refer(interfaceClass, url));</span><br><span class="line">                <span class="keyword">if</span> (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) &#123;</span><br><span class="line">                    registryURL = url;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (registryURL != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">// 如果注册中心链接不为空，则将使用 AvailableCluster</span></span><br><span class="line">                <span class="type">URL</span> <span class="variable">u</span> <span class="operator">=</span> registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);</span><br><span class="line">                <span class="comment">// 创建 StaticDirectory 实例，并由 Cluster 对多个 Invoker 进行合并</span></span><br><span class="line">                invoker = cluster.join(<span class="keyword">new</span> <span class="title class_">StaticDirectory</span>(u, invokers));</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                invoker = cluster.join(<span class="keyword">new</span> <span class="title class_">StaticDirectory</span>(invokers));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">c</span> <span class="operator">=</span> check;</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="literal">null</span> &amp;&amp; consumer != <span class="literal">null</span>) &#123;</span><br><span class="line">        c = consumer.isCheck();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (c == <span class="literal">null</span>) &#123;</span><br><span class="line">        c = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// invoker 可用性检查</span></span><br><span class="line">    <span class="keyword">if</span> (c &amp;&amp; !invoker.isAvailable()) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;No provider available for the service...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 生成代理类</span></span><br><span class="line">    <span class="keyword">return</span> (T) proxyFactory.getProxy(invoker);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码很多，不过逻辑比较清晰。首先根据配置检查是否为本地调用，若是，则调用 InjvmProtocol 的 refer 方法生成 InjvmInvoker 实例。若不是，则读取直联配置项，或注册中心 url，并将读取到的 url 存储到 urls 中。然后，根据 urls 元素数量进行后续操作。若 urls 元素数量为1，则直接通过 Protocol 自适应拓展构建 Invoker 实例接口。若 urls 元素数量大于1，即存在多个注册中心或服务直联 url，此时先根据 url 构建 Invoker。然后再通过 Cluster 合并多个 Invoker，最后调用 ProxyFactory 生成代理类。这里，Invoker 的构建过程以及代理类的过程比较重要，因此我将分两小节对这两个过程进行分析。</p><h4 id="3-2-1-创建-Invoker"><a href="#3-2-1-创建-Invoker" class="headerlink" title="3.2.1 创建 Invoker"></a>3.2.1 创建 Invoker</h4><p>Invoker 是 Dubbo 的核心模型，代表一个可执行体。在服务提供方，Invoker 用于调用服务提供类。在服务消费方，Invoker 用于执行远程调用。Invoker 在 Dubbo 中的位置十分重要，因此我们有必要去搞懂它。从前面的代码中可知，Invoker 是由 Protocol 实现类构建的。Protocol 实现类有很多，这里我会分析最常用的两个，分别是 RegistryProtocol 和 DubboProtocol，其他的大家自行分析。下面先来分析 DubboProtocol 的 refer 方法源码。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">refer</span><span class="params">(Class&lt;T&gt; serviceType, URL url)</span> <span class="keyword">throws</span> RpcException &#123;</span><br><span class="line">    optimizeSerialization(url);</span><br><span class="line">    <span class="comment">// 创建 DubboInvoker</span></span><br><span class="line">    DubboInvoker&lt;T&gt; invoker = <span class="keyword">new</span> <span class="title class_">DubboInvoker</span>&lt;T&gt;(serviceType, url, getClients(url), invokers);</span><br><span class="line">    invokers.add(invoker);</span><br><span class="line">    <span class="keyword">return</span> invoker;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面方法看起来比较简单，不过这里有一个调用需要我们注意一下，即 getClients。这个方法用于获取客户端实例，实例类型为 ExchangeClient。ExchangeClient 实际上并不具备通信能力，因此它需要更底层的客户端实例进行通信。比如 NettyClient、MinaClient 等，默认情况下，Dubbo 使用 NettyClient 进行通信。接下来，我们简单看一下 getClients 方法的逻辑。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> ExchangeClient[] getClients(URL url) &#123;</span><br><span class="line">    <span class="comment">// 是否共享连接</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">service_share_connect</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">      <span class="comment">// 获取连接数，默认为0，表示未配置</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">connections</span> <span class="operator">=</span> url.getParameter(Constants.CONNECTIONS_KEY, <span class="number">0</span>);</span><br><span class="line">    <span class="comment">// 如果未配置 connections，则共享连接</span></span><br><span class="line">    <span class="keyword">if</span> (connections == <span class="number">0</span>) &#123;</span><br><span class="line">        service_share_connect = <span class="literal">true</span>;</span><br><span class="line">        connections = <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ExchangeClient[] clients = <span class="keyword">new</span> <span class="title class_">ExchangeClient</span>[connections];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; clients.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (service_share_connect) &#123;</span><br><span class="line">            <span class="comment">// 获取共享客户端</span></span><br><span class="line">            clients[i] = getSharedClient(url);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 初始化新的客户端</span></span><br><span class="line">            clients[i] = initClient(url);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> clients;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里根据 connections 数量决定是获取共享客户端还是创建新的客户端实例，默认情况下，使用共享客户端实例。不过 getSharedClient 方法中也会调用 initClient 方法，因此下面我们一起看一下这两个方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> ExchangeClient <span class="title function_">getSharedClient</span><span class="params">(URL url)</span> &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> url.getAddress();</span><br><span class="line">    <span class="comment">// 获取带有“引用计数”功能的 ExchangeClient</span></span><br><span class="line">    <span class="type">ReferenceCountExchangeClient</span> <span class="variable">client</span> <span class="operator">=</span> referenceClientMap.get(key);</span><br><span class="line">    <span class="keyword">if</span> (client != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!client.isClosed()) &#123;</span><br><span class="line">            <span class="comment">// 增加引用计数</span></span><br><span class="line">            client.incrementAndGetCount();</span><br><span class="line">            <span class="keyword">return</span> client;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            referenceClientMap.remove(key);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    locks.putIfAbsent(key, <span class="keyword">new</span> <span class="title class_">Object</span>());</span><br><span class="line">    <span class="keyword">synchronized</span> (locks.get(key)) &#123;</span><br><span class="line">        <span class="keyword">if</span> (referenceClientMap.containsKey(key)) &#123;</span><br><span class="line">            <span class="keyword">return</span> referenceClientMap.get(key);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 创建 ExchangeClient 客户端</span></span><br><span class="line">        <span class="type">ExchangeClient</span> <span class="variable">exchangeClient</span> <span class="operator">=</span> initClient(url);</span><br><span class="line">        <span class="comment">// 将 ExchangeClient 实例传给 ReferenceCountExchangeClient，这里明显用了装饰模式</span></span><br><span class="line">        client = <span class="keyword">new</span> <span class="title class_">ReferenceCountExchangeClient</span>(exchangeClient, ghostClientMap);</span><br><span class="line">        referenceClientMap.put(key, client);</span><br><span class="line">        ghostClientMap.remove(key);</span><br><span class="line">        locks.remove(key);</span><br><span class="line">        <span class="keyword">return</span> client;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面方法先访问缓存，若缓存未命中，则通过 initClient 方法创建新的 ExchangeClient 实例，并将该实例传给 ReferenceCountExchangeClient 构造方法创建一个带有引用技术功能的 ExchangeClient 实例。ReferenceCountExchangeClient 内部实现比较简单，就不分析了。下面我们再来看一下 initClient 方法的代码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> ExchangeClient <span class="title function_">initClient</span><span class="params">(URL url)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取客户端类型，默认为 netty</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 添加编解码和心跳包参数到 url 中</span></span><br><span class="line">    url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);</span><br><span class="line">    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检测客户端类型是否存在，不存在则抛出异常</span></span><br><span class="line">    <span class="keyword">if</span> (str != <span class="literal">null</span> &amp;&amp; str.length() &gt; <span class="number">0</span> &amp;&amp; !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(<span class="string">&quot;Unsupported client type: ...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ExchangeClient client;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 获取 lazy 配置，并根据配置值决定创建的客户端类型</span></span><br><span class="line">        <span class="keyword">if</span> (url.getParameter(Constants.LAZY_CONNECT_KEY, <span class="literal">false</span>)) &#123;</span><br><span class="line">            <span class="comment">// 创建懒加载 ExchangeClient 实例</span></span><br><span class="line">            client = <span class="keyword">new</span> <span class="title class_">LazyConnectExchangeClient</span>(url, requestHandler);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// 创建普通 ExchangeClient 实例</span></span><br><span class="line">            client = Exchangers.connect(url, requestHandler);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemotingException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RpcException</span>(<span class="string">&quot;Fail to create remoting client for service...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> client;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>initClient 方法首先获取用户配置的客户端类型，默认为 netty。然后检测用户配置的客户端类型是否存在，不存在则抛出异常。最后根据 lazy 配置决定创建什么类型的客户端。这里的 LazyConnectExchangeClient 代码并不是很复杂，该类会在 request 方法被调用时通过 Exchangers 的 connect 方法创建 ExchangeClient 客户端，这里就不分析 LazyConnectExchangeClient 的代码了。下面我们分析一下 Exchangers 的 connect 方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExchangeClient <span class="title function_">connect</span><span class="params">(URL url, ExchangeHandler handler)</span> <span class="keyword">throws</span> RemotingException &#123;</span><br><span class="line">    <span class="keyword">if</span> (url == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;url == null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (handler == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;handler == null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    url = url.addParameterIfAbsent(Constants.CODEC_KEY, <span class="string">&quot;exchange&quot;</span>);</span><br><span class="line">    <span class="comment">// 获取 Exchanger 实例，默认为 HeaderExchangeClient</span></span><br><span class="line">    <span class="keyword">return</span> getExchanger(url).connect(url, handler);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，getExchanger 会通过 SPI 加载 HeaderExchangeClient 实例，这个方法比较简单，大家自己看一下吧。接下来分析 HeaderExchangeClient 的实现。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> ExchangeClient <span class="title function_">connect</span><span class="params">(URL url, ExchangeHandler handler)</span> <span class="keyword">throws</span> RemotingException &#123;</span><br><span class="line">    <span class="comment">// 这里包含了多个调用，分别如下：</span></span><br><span class="line">    <span class="comment">// 1. 创建 HeaderExchangeHandler 对象</span></span><br><span class="line">    <span class="comment">// 2. 创建 DecodeHandler 对象</span></span><br><span class="line">    <span class="comment">// 3. 通过 Transporters 构建 Client 实例</span></span><br><span class="line">    <span class="comment">// 4. 创建 HeaderExchangeClient 对象</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">HeaderExchangeClient</span>(Transporters.connect(url, <span class="keyword">new</span> <span class="title class_">DecodeHandler</span>(<span class="keyword">new</span> <span class="title class_">HeaderExchangeHandler</span>(handler))), <span class="literal">true</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的调用比较多，我们这里重点看一下 Transporters 的 connect 方法。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Client <span class="title function_">connect</span><span class="params">(URL url, ChannelHandler... handlers)</span> <span class="keyword">throws</span> RemotingException &#123;</span><br><span class="line">    <span class="keyword">if</span> (url == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;url == null&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    ChannelHandler handler;</span><br><span class="line">    <span class="keyword">if</span> (handlers == <span class="literal">null</span> || handlers.length == <span class="number">0</span>) &#123;</span><br><span class="line">        handler = <span class="keyword">new</span> <span class="title class_">ChannelHandlerAdapter</span>();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (handlers.length == <span class="number">1</span>) &#123;</span><br><span class="line">        handler = handlers[<span class="number">0</span>];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 如果 handler 数量大于1，则创建一个 ChannelHandler 分发器</span></span><br><span class="line">        handler = <span class="keyword">new</span> <span class="title class_">ChannelHandlerDispatcher</span>(handlers);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取 Transporter 自适应拓展类，并调用 connect 方法生成 Client 实例</span></span><br><span class="line">    <span class="keyword">return</span> getTransporter().connect(url, handler);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里，getTransporter 方法返回的是自适应拓展类，该类会在运行时根据客户端类型加载指定的 Transporter 实现类。若用户未显示配置客户端类型，则默认加载 NettyTransporter，并调用该类的 connect 方法。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Client <span class="title function_">connect</span><span class="params">(URL url, ChannelHandler listener)</span> <span class="keyword">throws</span> RemotingException &#123;</span><br><span class="line">    <span class="comment">// 创建 NettyClient 对象</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">NettyClient</span>(url, listener);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到这里就不继续跟下去了，在往下就是通过 Netty 提供的接口构建 Netty 客户端了，大家有兴趣自己看看。到这里，关于 DubboProtocol 的 refer 方法就分析完了。接下来，继续分析 RegistryProtocol 的 refer 方法逻辑。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">refer</span><span class="params">(Class&lt;T&gt; type, URL url)</span> <span class="keyword">throws</span> RpcException &#123;</span><br><span class="line">    <span class="comment">// 取 registry 参数值，并将其设置为协议头</span></span><br><span class="line">    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);</span><br><span class="line">    <span class="comment">// 获取注册中心实例</span></span><br><span class="line">    <span class="type">Registry</span> <span class="variable">registry</span> <span class="operator">=</span> registryFactory.getRegistry(url);</span><br><span class="line">    <span class="comment">// 这个判断暂时不知道有什么意图，为什么要给 RegistryService 类型生成 Invoker ？</span></span><br><span class="line">    <span class="keyword">if</span> (RegistryService.class.equals(type)) &#123;</span><br><span class="line">        <span class="keyword">return</span> proxyFactory.getInvoker((T) registry, type, url);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 将 url 查询字符串转为 Map</span></span><br><span class="line">    Map&lt;String, String&gt; qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));</span><br><span class="line">    <span class="comment">// 获取 group 配置</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">group</span> <span class="operator">=</span> qs.get(Constants.GROUP_KEY);</span><br><span class="line">    <span class="keyword">if</span> (group != <span class="literal">null</span> &amp;&amp; group.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ((Constants.COMMA_SPLIT_PATTERN.split(group)).length &gt; <span class="number">1</span></span><br><span class="line">                || <span class="string">&quot;*&quot;</span>.equals(group)) &#123;</span><br><span class="line">            <span class="comment">// 通过 SPI 加载 MergeableCluster 实例，并调用 doRefer 继续执行引用服务逻辑</span></span><br><span class="line">            <span class="keyword">return</span> doRefer(getMergeableCluster(), registry, type, url);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 调用 doRefer 继续执行引用服务逻辑</span></span><br><span class="line">    <span class="keyword">return</span> doRefer(cluster, registry, type, url);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码首先为 url 设置协议头，然后根据 url 参数加载注册中心实例。接下来对 RegistryService 继续针对性处理，这个处理逻辑我不是很明白，不知道为什么要为 RegistryService 类型生成 Invoker，有知道同学麻烦告知一下。然后就是获取 group 配置，根据 group 配置决定 doRefer 第一个参数的类型。这里的重点是 doRefer 方法，如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> &lt;T&gt; Invoker&lt;T&gt; <span class="title function_">doRefer</span><span class="params">(Cluster cluster, Registry registry, Class&lt;T&gt; type, URL url)</span> &#123;</span><br><span class="line">    <span class="comment">// 创建 RegistryDirectory 实例</span></span><br><span class="line">    RegistryDirectory&lt;T&gt; directory = <span class="keyword">new</span> <span class="title class_">RegistryDirectory</span>&lt;T&gt;(type, url);</span><br><span class="line">    <span class="comment">// 设置注册中心和协议</span></span><br><span class="line">    directory.setRegistry(registry);</span><br><span class="line">    directory.setProtocol(protocol);</span><br><span class="line">    Map&lt;String, String&gt; parameters = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, String&gt;(directory.getUrl().getParameters());</span><br><span class="line">    <span class="comment">// 生成服务消费者链接</span></span><br><span class="line">    <span class="type">URL</span> <span class="variable">subscribeUrl</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">URL</span>(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), <span class="number">0</span>, type.getName(), parameters);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注册服务消费者，在 consumers 目录下新节点</span></span><br><span class="line">    <span class="keyword">if</span> (!Constants.ANY_VALUE.equals(url.getServiceInterface())</span><br><span class="line">            &amp;&amp; url.getParameter(Constants.REGISTER_KEY, <span class="literal">true</span>)) &#123;</span><br><span class="line">        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,</span><br><span class="line">                Constants.CHECK_KEY, String.valueOf(<span class="literal">false</span>)));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 订阅 providers、configurators、routers 等节点数据</span></span><br><span class="line">    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,</span><br><span class="line">            Constants.PROVIDERS_CATEGORY</span><br><span class="line">                    + <span class="string">&quot;,&quot;</span> + Constants.CONFIGURATORS_CATEGORY</span><br><span class="line">                    + <span class="string">&quot;,&quot;</span> + Constants.ROUTERS_CATEGORY));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 一个注册中心可能有多个服务提供者，因此这里需要将多个服务提供者合并为一个</span></span><br><span class="line">    <span class="type">Invoker</span> <span class="variable">invoker</span> <span class="operator">=</span> cluster.join(directory);</span><br><span class="line">    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);</span><br><span class="line">    <span class="keyword">return</span> invoker;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如上，doRefer 方法创建一个 RegistryDirectory 实例，然后生成服务者消费者链接，并向注册中心进行注册。注册完毕后，紧接着订阅 providers、configurators、routers 等节点下的数据。完成订阅后，RegistryDirectory 会收到这几个节点下的子节点信息，比如可以获取到服务提供者的配置信息。由于一个服务可能部署在多台服务器上，这样就会在 providers 产生多个节点，这个时候就需要 Cluster 将多个服务节点合并为一个，并生成一个 Invoker。关于 RegistryDirectory 和 Cluster，本文不打算进行分析，相关分析将会在随后的文章中展开。</p><p>好了，关于 Invoker 的创建的逻辑就先分析到这。逻辑比较多，大家耐心看一下。</p><h4 id="3-2-2-创建代理"><a href="#3-2-2-创建代理" class="headerlink" title="3.2.2 创建代理"></a>3.2.2 创建代理</h4><p>Invoker 创建完毕后，接下来要做的事情是为服务接口生成代理对象。有了代理对象，我们就可以通过代理对象进行远程调用。代理对象生成的入口方法为在 ProxyFactory 的 getProxy，接下来进行分析。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; T <span class="title function_">getProxy</span><span class="params">(Invoker&lt;T&gt; invoker)</span> <span class="keyword">throws</span> RpcException &#123;</span><br><span class="line">    <span class="comment">// 调用重载方法</span></span><br><span class="line">    <span class="keyword">return</span> getProxy(invoker, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> &lt;T&gt; T <span class="title function_">getProxy</span><span class="params">(Invoker&lt;T&gt; invoker, <span class="type">boolean</span> generic)</span> <span class="keyword">throws</span> RpcException &#123;</span><br><span class="line">    Class&lt;?&gt;[] interfaces = <span class="literal">null</span>;</span><br><span class="line">    <span class="comment">// 获取接口列表</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">config</span> <span class="operator">=</span> invoker.getUrl().getParameter(<span class="string">&quot;interfaces&quot;</span>);</span><br><span class="line">    <span class="keyword">if</span> (config != <span class="literal">null</span> &amp;&amp; config.length() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="comment">// 切分接口列表</span></span><br><span class="line">        String[] types = Constants.COMMA_SPLIT_PATTERN.split(config);</span><br><span class="line">        <span class="keyword">if</span> (types != <span class="literal">null</span> &amp;&amp; types.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            interfaces = <span class="keyword">new</span> <span class="title class_">Class</span>&lt;?&gt;[types.length + <span class="number">2</span>];</span><br><span class="line">            <span class="comment">// 设置服务接口类和 EchoService.class 到 interfaces 中</span></span><br><span class="line">            interfaces[<span class="number">0</span>] = invoker.getInterface();</span><br><span class="line">            interfaces[<span class="number">1</span>] = EchoService.class;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; types.length; i++) &#123;</span><br><span class="line">                <span class="comment">// 加载接口类</span></span><br><span class="line">                interfaces[i + <span class="number">1</span>] = ReflectUtils.forName(types[i]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (interfaces == <span class="literal">null</span>) &#123;</span><br><span class="line">        interfaces = <span class="keyword">new</span> <span class="title class_">Class</span>&lt;?&gt;[]&#123;invoker.getInterface(), EchoService.class&#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 为 http 和 hessian 协议提供泛化调用支持，参考 pull request #1827</span></span><br><span class="line">    <span class="keyword">if</span> (!invoker.getInterface().equals(GenericService.class) &amp;&amp; generic) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> interfaces.length;</span><br><span class="line">        Class&lt;?&gt;[] temp = interfaces;</span><br><span class="line">        <span class="comment">// 创建新的 interfaces 数组</span></span><br><span class="line">        interfaces = <span class="keyword">new</span> <span class="title class_">Class</span>&lt;?&gt;[len + <span class="number">1</span>];</span><br><span class="line">        System.arraycopy(temp, <span class="number">0</span>, interfaces, <span class="number">0</span>, len);</span><br><span class="line">        <span class="comment">// 设置 GenericService.class 到数组中</span></span><br><span class="line">        interfaces[len] = GenericService.class;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 调用重载方法</span></span><br><span class="line">    <span class="keyword">return</span> getProxy(invoker, interfaces);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> &lt;T&gt; T <span class="title function_">getProxy</span><span class="params">(Invoker&lt;T&gt; invoker, Class&lt;?&gt;[] types)</span>;</span><br></pre></td></tr></table></figure><p>如上，上面大段代码都是用来获取 interfaces 数组的，因此我们需要继续往下看。getProxy(Invoker, Class&lt;&gt;[]) 这个方法是一个抽象方法，下面我们到 JavassistProxyFactory 类中看一下该方法的实现代码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> &lt;T&gt; T <span class="title function_">getProxy</span><span class="params">(Invoker&lt;T&gt; invoker, Class&lt;?&gt;[] interfaces)</span> &#123;</span><br><span class="line">    <span class="comment">// 生成 Proxy 子类（Proxy 是抽象类）。并调用Proxy 子类的 newInstance 方法生成 Proxy 实例</span></span><br><span class="line">    <span class="keyword">return</span> (T) Proxy.getProxy(interfaces).newInstance(<span class="keyword">new</span> <span class="title class_">InvokerInvocationHandler</span>(invoker));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码并不多，首先是通过 Proxy 的 getProxy 方法获取 Proxy 子类，然后创建 InvokerInvocationHandler 对象，并将该对象传给 newInstance 生成 Proxy 实例。InvokerInvocationHandler 实现自 JDK 的 InvocationHandler 接口，具体的用途是拦截接口类调用。该类逻辑比较简单，这里就不分析了。下面我们重点关注一下 Proxy 的 getProxy 方法，如下。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Proxy <span class="title function_">getProxy</span><span class="params">(Class&lt;?&gt;... ics)</span> &#123;</span><br><span class="line">    <span class="comment">// 调用重载方法</span></span><br><span class="line">    <span class="keyword">return</span> getProxy(ClassHelper.getClassLoader(Proxy.class), ics);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Proxy <span class="title function_">getProxy</span><span class="params">(ClassLoader cl, Class&lt;?&gt;... ics)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (ics.length &gt; <span class="number">65535</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;interface limit exceeded&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="type">StringBuilder</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">    <span class="comment">// 遍历接口列表</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; ics.length; i++) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">itf</span> <span class="operator">=</span> ics[i].getName();</span><br><span class="line">        <span class="comment">// 检测类型是否为接口</span></span><br><span class="line">        <span class="keyword">if</span> (!ics[i].isInterface())</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(itf + <span class="string">&quot; is not a interface.&quot;</span>);</span><br><span class="line"></span><br><span class="line">        Class&lt;?&gt; tmp = <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 重新加载接口类</span></span><br><span class="line">            tmp = Class.forName(itf, <span class="literal">false</span>, cl);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 检测接口是否相同，这里 tmp 有可能为空</span></span><br><span class="line">        <span class="keyword">if</span> (tmp != ics[i])</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(ics[i] + <span class="string">&quot; is not visible from class loader&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 拼接接口全限定名，分隔符为 ;</span></span><br><span class="line">        sb.append(itf).append(<span class="string">&#x27;;&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 使用拼接后接口名作为 key</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">key</span> <span class="operator">=</span> sb.toString();</span><br><span class="line"></span><br><span class="line">    Map&lt;String, Object&gt; cache;</span><br><span class="line">    <span class="keyword">synchronized</span> (ProxyCacheMap) &#123;</span><br><span class="line">        cache = ProxyCacheMap.get(cl);</span><br><span class="line">        <span class="keyword">if</span> (cache == <span class="literal">null</span>) &#123;</span><br><span class="line">            cache = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String, Object&gt;();</span><br><span class="line">            ProxyCacheMap.put(cl, cache);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">Proxy</span> <span class="variable">proxy</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">synchronized</span> (cache) &#123;</span><br><span class="line">        <span class="keyword">do</span> &#123;</span><br><span class="line">            <span class="comment">// 从缓存中获取 Reference&lt;Proxy&gt; 实例</span></span><br><span class="line">            <span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> cache.get(key);</span><br><span class="line">            <span class="keyword">if</span> (value <span class="keyword">instanceof</span> Reference&lt;?&gt;) &#123;</span><br><span class="line">                proxy = (Proxy) ((Reference&lt;?&gt;) value).get();</span><br><span class="line">                <span class="keyword">if</span> (proxy != <span class="literal">null</span>) &#123;</span><br><span class="line">                    <span class="keyword">return</span> proxy;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 多线程控制，保证只有一个线程可以进行后续操作</span></span><br><span class="line">            <span class="keyword">if</span> (value == PendingGenerationMarker) &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="comment">// 其他线程在此处进行等待</span></span><br><span class="line">                    cache.wait();</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// 放置标志位到缓存中，并跳出 while 循环进行后续操作</span></span><br><span class="line">                cache.put(key, PendingGenerationMarker);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">while</span> (<span class="literal">true</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">id</span> <span class="operator">=</span> PROXY_CLASS_COUNTER.getAndIncrement();</span><br><span class="line">    <span class="type">String</span> <span class="variable">pkg</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">ClassGenerator</span> <span class="variable">ccp</span> <span class="operator">=</span> <span class="literal">null</span>, ccm = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="comment">// 创建 ClassGenerator 对象</span></span><br><span class="line">        ccp = ClassGenerator.newInstance(cl);</span><br><span class="line"></span><br><span class="line">        Set&lt;String&gt; worked = <span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;String&gt;();</span><br><span class="line">        List&lt;Method&gt; methods = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;Method&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; ics.length; i++) &#123;</span><br><span class="line">            <span class="comment">// 检测接口访问级别是否为 protected 或 privete</span></span><br><span class="line">            <span class="keyword">if</span> (!Modifier.isPublic(ics[i].getModifiers())) &#123;</span><br><span class="line">                <span class="comment">// 获取接口包名</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">npkg</span> <span class="operator">=</span> ics[i].getPackage().getName();</span><br><span class="line">                <span class="keyword">if</span> (pkg == <span class="literal">null</span>) &#123;</span><br><span class="line">                    pkg = npkg;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="keyword">if</span> (!pkg.equals(npkg))</span><br><span class="line">                        <span class="comment">// 非 public 级别的接口必须在同一个包下，否者抛出异常</span></span><br><span class="line">                        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;non-public interfaces from different packages&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 添加接口到 ClassGenerator 中</span></span><br><span class="line">            ccp.addInterface(ics[i]);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 遍历接口方法</span></span><br><span class="line">            <span class="keyword">for</span> (Method method : ics[i].getMethods()) &#123;</span><br><span class="line">                <span class="comment">// 获取方法描述，可理解为方法签名</span></span><br><span class="line">                <span class="type">String</span> <span class="variable">desc</span> <span class="operator">=</span> ReflectUtils.getDesc(method);</span><br><span class="line">                <span class="comment">// 如果已包含在 worked 中，则忽略。考虑这种情况，</span></span><br><span class="line">                <span class="comment">// A 接口和 B 接口中包含一个完全相同的方法</span></span><br><span class="line">                <span class="keyword">if</span> (worked.contains(desc))</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                worked.add(desc);</span><br><span class="line"></span><br><span class="line">                <span class="type">int</span> <span class="variable">ix</span> <span class="operator">=</span> methods.size();</span><br><span class="line">                <span class="comment">// 获取方法返回值类型</span></span><br><span class="line">                Class&lt;?&gt; rt = method.getReturnType();</span><br><span class="line">                <span class="comment">// 获取参数列表</span></span><br><span class="line">                Class&lt;?&gt;[] pts = method.getParameterTypes();</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 生成 Object[] args = new Object[1...N]</span></span><br><span class="line">                <span class="type">StringBuilder</span> <span class="variable">code</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(<span class="string">&quot;Object[] args = new Object[&quot;</span>).append(pts.length).append(<span class="string">&quot;];&quot;</span>);</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; pts.length; j++)</span><br><span class="line">                    <span class="comment">// 生成 args[1...N] = ($w)$1...N;</span></span><br><span class="line">                    code.append(<span class="string">&quot; args[&quot;</span>).append(j).append(<span class="string">&quot;] = ($w)$&quot;</span>).append(j + <span class="number">1</span>).append(<span class="string">&quot;;&quot;</span>);</span><br><span class="line">                <span class="comment">// 生成 InvokerHandler 接口的 invoker 方法调用语句，如下：</span></span><br><span class="line">                <span class="comment">// Object ret = handler.invoke(this, methods[1...N], args);</span></span><br><span class="line">                code.append(<span class="string">&quot; Object ret = handler.invoke(this, methods[&quot;</span> + ix + <span class="string">&quot;], args);&quot;</span>);</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 返回值不为 void</span></span><br><span class="line">                <span class="keyword">if</span> (!Void.TYPE.equals(rt))</span><br><span class="line">                    <span class="comment">// 生成返回语句，形如 return (java.lang.String) ret;</span></span><br><span class="line">                    code.append(<span class="string">&quot; return &quot;</span>).append(asArgument(rt, <span class="string">&quot;ret&quot;</span>)).append(<span class="string">&quot;;&quot;</span>);</span><br><span class="line"></span><br><span class="line">                methods.add(method);</span><br><span class="line">                <span class="comment">// 添加方法名、访问控制符、参数列表、方法代码等信息到 ClassGenerator 中 </span></span><br><span class="line">                ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (pkg == <span class="literal">null</span>)</span><br><span class="line">            pkg = PACKAGE_NAME;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 构建接口代理类名称：pkg + &quot;.proxy&quot; + id，比如 com.tianxiaobo.proxy0</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">pcn</span> <span class="operator">=</span> pkg + <span class="string">&quot;.proxy&quot;</span> + id;</span><br><span class="line">        ccp.setClassName(pcn);</span><br><span class="line">        ccp.addField(<span class="string">&quot;public static java.lang.reflect.Method[] methods;&quot;</span>);</span><br><span class="line">        <span class="comment">// 生成 private java.lang.reflect.InvocationHandler handler;</span></span><br><span class="line">        ccp.addField(<span class="string">&quot;private &quot;</span> + InvocationHandler.class.getName() + <span class="string">&quot; handler;&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 为接口代理类添加带有 InvocationHandler 参数的构造方法，比如：</span></span><br><span class="line">        <span class="comment">// porxy0(java.lang.reflect.InvocationHandler arg0) &#123;</span></span><br><span class="line">        <span class="comment">//     handler=$1;</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line">        ccp.addConstructor(Modifier.PUBLIC, <span class="keyword">new</span> <span class="title class_">Class</span>&lt;?&gt;[]&#123;InvocationHandler.class&#125;, <span class="keyword">new</span> <span class="title class_">Class</span>&lt;?&gt;[<span class="number">0</span>], <span class="string">&quot;handler=$1;&quot;</span>);</span><br><span class="line">        <span class="comment">// 为接口代理类添加默认构造方法</span></span><br><span class="line">        ccp.addDefaultConstructor();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 生成接口代理类</span></span><br><span class="line">        Class&lt;?&gt; clazz = ccp.toClass();</span><br><span class="line">        clazz.getField(<span class="string">&quot;methods&quot;</span>).set(<span class="literal">null</span>, methods.toArray(<span class="keyword">new</span> <span class="title class_">Method</span>[<span class="number">0</span>]));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 构建 Proxy 子类名称，比如 Proxy1，Proxy2 等</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">fcn</span> <span class="operator">=</span> Proxy.class.getName() + id;</span><br><span class="line">        ccm = ClassGenerator.newInstance(cl);</span><br><span class="line">        ccm.setClassName(fcn);</span><br><span class="line">        ccm.addDefaultConstructor();</span><br><span class="line">        ccm.setSuperClass(Proxy.class);</span><br><span class="line">        <span class="comment">// 为 Proxy 的抽象方法 newInstance 生成实现代码，形如：</span></span><br><span class="line">        <span class="comment">// public Object newInstance(java.lang.reflect.InvocationHandler h) &#123; </span></span><br><span class="line">        <span class="comment">//     return new com.tianxiaobo.proxy0($1);</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line">        ccm.addMethod(<span class="string">&quot;public Object newInstance(&quot;</span> + InvocationHandler.class.getName() + <span class="string">&quot; h)&#123; return new &quot;</span> + pcn + <span class="string">&quot;($1); &#125;&quot;</span>);</span><br><span class="line">        <span class="comment">// 生成 Proxy 实现类</span></span><br><span class="line">        Class&lt;?&gt; pc = ccm.toClass();</span><br><span class="line">        <span class="comment">// 通过反射创建 Proxy 实例</span></span><br><span class="line">        proxy = (Proxy) pc.newInstance();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RuntimeException e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> e;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e.getMessage(), e);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (ccp != <span class="literal">null</span>)</span><br><span class="line">            <span class="comment">// 释放资源</span></span><br><span class="line">            ccp.release();</span><br><span class="line">        <span class="keyword">if</span> (ccm != <span class="literal">null</span>)</span><br><span class="line">            ccm.release();</span><br><span class="line">        <span class="keyword">synchronized</span> (cache) &#123;</span><br><span class="line">            <span class="keyword">if</span> (proxy == <span class="literal">null</span>)</span><br><span class="line">                cache.remove(key);</span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                <span class="comment">// 写缓存</span></span><br><span class="line">                cache.put(key, <span class="keyword">new</span> <span class="title class_">WeakReference</span>&lt;Proxy&gt;(proxy));</span><br><span class="line">            <span class="comment">// 唤醒其他等待线程</span></span><br><span class="line">            cache.notifyAll();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> proxy;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>上面代码比较复杂，我也写了很多注释。大家在阅读这段代码时，要搞清楚 ccp 和 ccm 的用途，不然会被搞晕。ccp 用于为服务接口生成代理类，比如我们有一个 DemoService 接口，这个接口代理类就是由 ccp 生成的。ccm 则是用于为 org.apache.dubbo.common.bytecode.Proxy 抽象类生成子类，主要是实现 Proxy 的抽象方法。下面以 org.apache.dubbo.demo.DemoService 这个接口为例，来看一下该接口代理类代码大致是怎样的（忽略 EchoService 接口）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> org.apache.dubbo.common.bytecode;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">proxy0</span> <span class="keyword">implements</span> <span class="title class_">org</span>.apache.dubbo.demo.DemoService &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> java.lang.reflect.Method[] methods;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> java.lang.reflect.InvocationHandler handler;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">proxy0</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">proxy0</span><span class="params">(java.lang.reflect.InvocationHandler arg0)</span> &#123;</span><br><span class="line">        handler = $<span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> java.lang.String <span class="title function_">sayHello</span><span class="params">(java.lang.String arg0)</span> &#123;</span><br><span class="line">        Object[] args = <span class="keyword">new</span> <span class="title class_">Object</span>[<span class="number">1</span>];</span><br><span class="line">        args[<span class="number">0</span>] = ($w) $<span class="number">1</span>;</span><br><span class="line">        <span class="type">Object</span> <span class="variable">ret</span> <span class="operator">=</span> handler.invoke(<span class="built_in">this</span>, methods[<span class="number">0</span>], args);</span><br><span class="line">        <span class="keyword">return</span> (java.lang.String) ret;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>好了，到这里代理类生成逻辑就分析完了。整个过程比较复杂，大家需要耐心看一下，本节点到这里。</p><h2 id="4-总结"><a href="#4-总结" class="headerlink" title="4.总结"></a>4.总结</h2><p>本篇文章对服务引用的过程进行了较为详尽的分析，之所以说是较为详尽，是因为还有一些地方没有分析到。比如 Directory、Cluster 等实现类的代码并未进行详细分析，由于这些类功能比较独立，因此我打算后续单独成文进行分析。暂时我们可以先把这些类看成黑盒，只要知道这些类的用途即可。引用服务过程涉及到的调用也非常多，大家在阅读相关代码的中耐心些，并多进行调试。</p><p>好了，本篇文章就先到这里了。谢谢阅读。</p></article><div class="post-copyright"><div class="copyright-cc-box"><i class="anzhiyufont anzhiyu-icon-copyright"></i></div><div class="post-copyright__author_box"><a class="post-copyright__author_img" href="/" title="头像"><img class="post-copyright__author_img_back" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"><img class="post-copyright__author_img_front" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" title="头像" alt="头像"></a><div class="post-copyright__author_name">云少</div><div class="post-copyright__author_desc">站在巨人的肩膀罢了</div></div><div class="post-copyright__post__info"><a class="post-copyright__original" title="该文章为原创文章，注意版权协议" href="https://it985.github.io/posts/d72547db.html">原创</a><a class="post-copyright-title"><span onclick='rm.copyPageUrl("https://it985.github.io/posts/d72547db.html")'>Dubbo 源码分析 – 服务引用</span></a></div><div class="post-tools" id="post-tools"><div class="post-tools-left"><div class="rewardLeftButton"><div class="post-reward" onclick="anzhiyu.addRewardMask()"><div class="reward-button button--animated" title="赞赏作者"><i class="anzhiyufont anzhiyu-icon-hand-heart-fill"></i>打赏作者</div><div class="reward-main"><div class="reward-all"><span class="reward-title">感谢你赐予我前进的力量</span><ul class="reward-group"><li class="reward-item"><a href="/img/wxpay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/wxpay.webp" alt="wechat"></a><div class="post-qr-code-desc">wechat</div></li><li class="reward-item"><a href="/img/alipay.webp" target="_blank"><img class="post-qr-code-img" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/alipay.webp" alt="alipay"></a><div class="post-qr-code-desc">alipay</div></li></ul><a class="reward-main-btn" href="/about/#about-reward" target="_blank"><div class="reward-text">赞赏者名单</div><div class="reward-dec">因为你们的支持让我意识到写文章的价值🙏</div></a></div></div></div><div id="quit-box" onclick="anzhiyu.removeRewardMask()" style="display:none"></div></div><div class="shareRight"><div class="share-link mobile"><div class="share-qrcode"><div class="share-button" title="使用手机访问这篇文章"><i class="anzhiyufont anzhiyu-icon-qrcode"></i></div><div class="share-main"><div class="share-main-all"><div id="qrcode" title="https://it985.github.io/posts/d72547db.html"></div><div class="reward-dec">使用手机访问这篇文章</div></div></div></div></div><div class="share-link weibo"><a class="share-button" target="_blank" href="https://service.weibo.com/share/share.php?title=Dubbo 源码分析 – 服务引用&amp;url=https://it985.github.io/posts/d72547db.html&amp;pic=https://www.bing.com/th?id=OHR.HedgehogNest_EN-GB2384367951_UHD.jpg" rel="external nofollow noreferrer noopener"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a></div><div class="share-link copyurl"><div class="share-button" id="post-share-url" title="复制链接" onclick="rm.copyPageUrl()"><i class="anzhiyufont anzhiyu-icon-link"></i></div></div></div></div></div><div class="post-copyright__notice"><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://it985.github.io" target="_blank">云少IT</a>！</span></div></div><div class="post-tools-right"><div class="tag_share"><div class="post-meta__box"><div class="post-meta__box__tag-list"></div></div><div class="post_share"><div class="social-share" data-image="https://img02.anheyu.com/adminuploads/1/2022/09/05/6315e146a8bbd.webp" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/css/share.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/sharejs/dist/js/social-share.min.js" defer></script></div></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/posts/bc8dae89.html"><img class="prev-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.bing.com/th?id=OHR.BadLightning_EN-GB7102993068_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Dubbo 源码分析 – 服务导出</div></div></a></div><div class="next-post pull-right"><a href="/posts/1c48ee72.html"><img class="next-cover" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.bing.com/th?id=OHR.CrestedButteEclispe_EN-GB1381305687_UHD.jpg" onerror='onerror=null,src="/img/404.jpg"' alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">Dubbo 源码分析 – 集群容错之Directory</div></div></a></div></nav><hr><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="anzhiyufont anzhiyu-icon-comments"></i><span> 评论</span></div><div class="comment-randomInfo"><a onclick="anzhiyu.addRandomCommentInfo()" href="javascript:void(0)" rel="external nofollow noreferrer">匿名评论</a><a href="/privacy" style="margin-left:4px">隐私政策</a></div><div class="comment-switch"><span class="first-comment">Twikoo</span><span id="switch-btn"></span><span class="second-comment">Artalk</span></div><div class="comment-tips" id="comment-tips"><span>✅ 你无需删除空行，直接评论以获取最佳展示效果</span></div></div><div class="comment-wrap"><div><div id="twikoo-wrap"></div></div><div><div id="artalk-wrap"></div></div></div></div><div class="comment-barrage"></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="anzhiyufont anzhiyu-icon-bars"></i><span>文章目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%9B%9B%E3%80%81Dubbo-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E2%80%93-%E6%9C%8D%E5%8A%A1%E5%BC%95%E7%94%A8"><span class="toc-number">1.</span> <span class="toc-text">四、Dubbo 源码分析 – 服务引用</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#1-%E7%AE%80%E4%BB%8B"><span class="toc-number">1.1.</span> <span class="toc-text">1. 简介</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2-%E6%9C%8D%E5%8A%A1%E5%BC%95%E7%94%A8%E5%8E%9F%E7%90%86"><span class="toc-number">1.2.</span> <span class="toc-text">2.服务引用原理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90"><span class="toc-number">1.3.</span> <span class="toc-text">3.源码分析</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#3-1-%E5%A4%84%E7%90%86%E9%85%8D%E7%BD%AE"><span class="toc-number">1.3.1.</span> <span class="toc-text">3.1 处理配置</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-2-%E5%BC%95%E7%94%A8%E6%9C%8D%E5%8A%A1"><span class="toc-number">1.3.2.</span> <span class="toc-text">3.2 引用服务</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#3-2-1-%E5%88%9B%E5%BB%BA-Invoker"><span class="toc-number">1.3.2.1.</span> <span class="toc-text">3.2.1 创建 Invoker</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-2-2-%E5%88%9B%E5%BB%BA%E4%BB%A3%E7%90%86"><span class="toc-number">1.3.2.2.</span> <span class="toc-text">3.2.2 创建代理</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4-%E6%80%BB%E7%BB%93"><span class="toc-number">1.4.</span> <span class="toc-text">4.总结</span></a></li></ol></li></ol></div></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div id="footer_deal"><a class="deal_link" href="mailto:2071916845@qq.com" rel="external nofollow noreferrer" title="email"><i class="anzhiyufont anzhiyu-icon-envelope"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://weibo.com/" title="微博"><i class="anzhiyufont anzhiyu-icon-weibo"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.facebook.com/" title="facebook"><i class="anzhiyufont anzhiyu-icon-facebook1"></i></a><a class="deal_link" href="/atom.xml" title="RSS"><i class="anzhiyufont anzhiyu-icon-rss"></i></a><img class="footer_mini_logo" title="返回顶部" alt="返回顶部" onclick="anzhiyu.scrollToDest(0,500)" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://q1.qlogo.cn/g?b=qq&amp;nk=2071916845&amp;s=640" size="50px"><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/it985" title="Github"><i class="anzhiyufont anzhiyu-icon-github"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://space.bilibili.com/300767383" title="Bilibili"><i class="anzhiyufont anzhiyu-icon-bilibili"></i></a><a class="deal_link" target="_blank" rel="noopener external nofollow noreferrer" href="https://v.douyin.com/" title="抖音"><i class="anzhiyufont anzhiyu-icon-tiktok"></i></a><a class="deal_link" href="/copyright" title="CC"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i></a></div><div id="anzhiyu-footer"><div class="footer-group"><div class="footer-title">服务</div><div class="footer-links"><a class="footer-item" title="51la统计" target="_blank" rel="noopener external nofollow noreferrer" href="https://v6.51.la/">51la统计</a><a class="footer-item" title="十年之约" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.foreverblog.cn/">十年之约</a><a class="footer-item" title="开往" target="_blank" rel="noopener external nofollow noreferrer" href="https://github.com/travellings-link/travellings">开往</a></div></div><div class="footer-group"><div class="footer-title">导航</div><div class="footer-links"><a class="footer-item" title="即刻短文" href="/essay/">即刻短文</a><a class="footer-item" title="友链文章" href="/fcircle/">友链文章</a><a class="footer-item" title="留言板" href="/comments/">留言板</a></div></div><div class="footer-group"><div class="footer-title">协议</div><div class="footer-links"><a class="footer-item" title="免责声明" href="/disclaimer/">免责声明</a><a class="footer-item" title="隐私协议" href="/privacy/">隐私协议</a><a class="footer-item" title="Cookies" href="/cookies/">Cookies</a><a class="footer-item" title="版权协议" href="/copyright/">版权协议</a></div></div><div class="footer-group"><div class="footer-title">娱乐</div><div class="footer-links"><a class="footer-item" title="小空调" href="/air-conditioner/">小空调</a><a class="footer-item" title="围住小猫" href="/catch-cat/">围住小猫</a><a class="footer-item" title="免费图床" href="/huluxiapicture/">免费图床</a></div></div><div class="footer-group"><div class="footer-title">推荐分类</div><div class="footer-links"><a class="footer-item" title="Java" href="/categories/Java/">Java</a><a class="footer-item" title="面试" href="/categories/%E9%9D%A2%E8%AF%95/">面试</a></div></div><div class="footer-group"><div class="footer-title-group"><div class="footer-title">友链</div><a class="random-friends-btn" id="footer-random-friends-btn" href="javascript:addFriendLinksInFooter();" rel="external nofollow noreferrer" title="换一批友情链接"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right"></i></a></div><div class="footer-links" id="friend-links-in-footer"></div></div></div><div id="workboard"><img class="workSituationImg boardsign" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-上班摸鱼中.svg" alt="距离月入25k也就还差一个大佬带我~" title="距离月入25k也就还差一个大佬带我~"><div id="runtimeTextTip"></div></div><div class="wordcount"></div><span>云少已经写了 996.7k 字，</span><span>好像写完一本我国著名的 四大名著 了！！！</span><p id="ghbdages"><a class="github-badge" target="_blank" href="https://hexo.io/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="博客框架为Hexo_v5.4.0" title="博客框架为Hexo_v5.4.0"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Frame-Hexo.svg" alt="博客框架为Hexo_v5.4.0"></a><a class="github-badge" target="_blank" href="https://www.upyun.com/?utm_source=lianmeng&amp;utm_medium=referral" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站使用又拍云为静态资源提供CDN加速" title="本站使用又拍云为静态资源提供CDN加速"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/CDN-%E5%8F%88%E6%8B%8D%E4%BA%91-orange%3Fstyle%3Dflat%26logo%3D%E5%8F%88%E6%8B%8D%E4%BA%91" alt="本站使用又拍云为静态资源提供CDN加速"></a><a class="github-badge" target="_blank" href="https://github.com/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站项目由Github托管" title="本站项目由Github托管"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.1.5/img/badge/Source-Github.svg" alt="本站项目由Github托管"></a><a class="github-badge" target="_blank" href="http://creativecommons.org/licenses/by-nc-sa/4.0/" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可" title="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://npm.elemecdn.com/anzhiyu-blog@2.2.0/img/badge/Copyright-BY-NC-SA.svg" alt="本站采用知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可"></a><a class="github-badge" target="_blank" href="https://icp.gov.moe/?keyword=20221607" rel="external nofollow noreferrer" style="margin-inline:5px" data-title="萌ICP备20221607号" title="萌ICP备20221607号"><img src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://img.shields.io/badge/%E8%90%8CICP%E5%A4%87-20221607-fe1384?style-flat&amp;logo=" alt="萌ICP备20221607号"></a></p></div><div id="footer-bar"><div class="footer-bar-links"><div class="footer-bar-left"><div id="footer-bar-tips"><div class="copyright">&copy;2018 - 2023 By <a class="footer-bar-link" href="/" title="云少" target="_blank">云少</a></div></div><div id="footer-type-tips"></div><div class="js-pjax"><script>function subtitleType(){fetch("https://v1.hitokoto.cn").then(t=>t.json()).then(t=>{var e="出自 "+t.from,p=[];p.unshift(t.hitokoto,e),window.typed=new Typed("#footer-type-tips",{strings:p,startDelay:300,typeSpeed:150,loop:!0,backSpeed:50})})}"function"==typeof Typed?subtitleType():getScript("https://cdn.cbd.int/typed.js@2.0.15/dist/typed.umd.js").then(subtitleType)</script></div></div><div class="footer-bar-right"><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="云少">云少</a><a class="footer-bar-link" target="_blank" rel="noopener external nofollow noreferrer" href="https://beian.miit.gov.cn/" title="鄂ICP备2021021095号-2">鄂ICP备2021021095号-2</a><a class="footer-bar-link cc" href="/copyright" title="cc协议"><i class="anzhiyufont anzhiyu-icon-copyright-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-by-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nc-line"></i><i class="anzhiyufont anzhiyu-icon-creative-commons-nd-line"></i></a></div></div></div></footer></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="sidebar-site-data site-data is-center"><a href="/archives/" title="archive"><div class="headline">文章</div><div class="length-num">861</div></a><a href="/tags/" title="tag"><div class="headline">标签</div><div class="length-num">35</div></a><a href="/categories/" title="category"><div class="headline">分类</div><div class="length-num">6</div></a></div><span class="sidebar-menu-item-title">功能</span><div class="sidebar-menu-item"><a class="darkmode_switchbutton menu-child" href="javascript:void(0);" rel="external nofollow noreferrer" title="显示模式"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span>显示模式</span></a></div><div class="back-menu-list-groups"><div class="back-menu-list-group"><div class="back-menu-list-title">网页</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top/" title="博客"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="/img/favicon.ico" alt="博客"><span class="back-menu-item-text">博客</span></a></div></div><div class="back-menu-list-group"><div class="back-menu-list-title">项目</div><div class="back-menu-list"><a class="back-menu-item" target="_blank" rel="noopener external nofollow noreferrer" href="https://www.tryrun.top" title="图床"><img class="back-menu-item-icon" src= "" onerror="this.onerror=null,this.src=&quot;/img/404.jpg&quot;" data-lazy-src="https://www.tryrun.top/favicon.ico" alt="图床"><span class="back-menu-item-text">图床</span></a></div></div></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>望四方</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/archives/"><i class="anzhiyufont anzhiyu-icon-box-archive faa-tada" style="font-size:.9em"></i><span> 归名档</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/categories/"><i class="anzhiyufont anzhiyu-icon-shapes faa-tada" style="font-size:.9em"></i><span> 归四类</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags faa-tada" style="font-size:.9em"></i><span> 书中签</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/charts/"><i class="fa-fw fas fa-chart-bar faa-tada"></i><span> 统计</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>友链</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/link/"><i class="anzhiyufont anzhiyu-icon-link faa-tada" style="font-size:.9em"></i><span> 四方好友</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/fcircle/"><i class="anzhiyufont anzhiyu-icon-artstation faa-tada" style="font-size:.9em"></i><span> 朋友圈</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/comments/"><i class="anzhiyufont anzhiyu-icon-envelope faa-tada" style="font-size:.9em"></i><span> 留言板</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>我的</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/bangumis/"><i class="anzhiyufont anzhiyu-icon-bilibili faa-tada" style="font-size:.9em"></i><span> 追番页</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/album/"><i class="anzhiyufont anzhiyu-icon-images faa-tada" style="font-size:.9em"></i><span> 刹那间</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/equipment/"><i class="fas fa-heart faa-tada"></i><span> 我的装备</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/collect/"><i class="fas fa-camcorder faa-tada"></i><span> 观影阁</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="javascript:void(0);" rel="external nofollow noreferrer"><span>关于</span></a><ul class="menus_item_child"><li><a class="site-page child faa-parent animated-hover" href="/about/"><i class="anzhiyufont anzhiyu-icon-paper-plane faa-tada" style="font-size:.9em"></i><span> 关于本人</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/essay/"><i class="anzhiyufont anzhiyu-icon-lightbulb faa-tada" style="font-size:.9em"></i><span> 闲言碎语</span></a></li><li><a class="site-page child faa-parent animated-hover" href="javascript:toRandomPost()" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-shoe-prints1 faa-tada" style="font-size:.9em"></i><span> 随便逛逛</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/disclaimer/"><i class="fas fa-heart faa-tada"></i><span> 免责声明</span></a></li><li><a class="site-page child faa-parent animated-hover" href="/love/"><i class="anzhiyufont anzhiyu-icon-heartbeat faa-tada" style="font-size:.9em"></i><span> 恋爱小屋</span></a></li></ul></div></div><span class="sidebar-menu-item-title">标签</span><div class="card-tags"><div class="item-headline"></div><div class="card-tag-cloud"><a href="/tags/API/" style="font-size:.88rem;color:#758692">API<sup>43</sup></a><a href="/tags/Base64/" style="font-size:.88rem;color:#2584bf">Base64<sup>1</sup></a><a href="/tags/Collectors/" style="font-size:.88rem;color:#bfad3e">Collectors<sup>3</sup></a><a href="/tags/Date/" style="font-size:.88rem;color:#b9b609">Date<sup>3</sup></a><a href="/tags/Executor/" style="font-size:.88rem;color:#14c83c">Executor<sup>9</sup></a><a href="/tags/Guava/" style="font-size:.88rem;color:#0a1878">Guava<sup>1</sup></a><a href="/tags/JVM/" style="font-size:.88rem;color:#625421">JVM<sup>8</sup></a><a href="/tags/Java8/" style="font-size:.88rem;color:#aa71bf">Java8<sup>21</sup></a><a href="/tags/Java9/" style="font-size:.88rem;color:#ad8774">Java9<sup>21</sup></a><a href="/tags/Java%E5%B9%B6%E5%8F%91/" style="font-size:.88rem;color:#19704c">Java并发<sup>20</sup></a><a href="/tags/Lambda/" style="font-size:.88rem;color:#7e5c78">Lambda<sup>4</sup></a><a href="/tags/Lock/" style="font-size:.88rem;color:#700769">Lock<sup>1</sup></a><a href="/tags/Maven/" style="font-size:.88rem;color:#2b8671">Maven<sup>1</sup></a><a href="/tags/Memcached/" style="font-size:.88rem;color:#1b0694">Memcached<sup>23</sup></a><a href="/tags/Mongodb/" style="font-size:.88rem;color:#36202d">Mongodb<sup>49</sup></a><a href="/tags/Queue/" style="font-size:.88rem;color:#643f76">Queue<sup>1</sup></a><a href="/tags/Redis/" style="font-size:.88rem;color:#1e4c38">Redis<sup>27</sup></a><a href="/tags/Stream/" style="font-size:.88rem;color:#a5a097">Stream<sup>4</sup></a><a href="/tags/Thread/" style="font-size:.88rem;color:#05682d">Thread<sup>7</sup></a><a href="/tags/Thread-pool/" style="font-size:.88rem;color:#38ad2a">Thread pool<sup>7</sup></a><a href="/tags/forkJoinPool/" style="font-size:.88rem;color:#827a6b">forkJoinPool<sup>2</sup></a><a href="/tags/stream/" style="font-size:.88rem;color:#4a3ac7">stream<sup>1</sup></a><a href="/tags/%E4%B8%93%E6%A0%8F/" style="font-size:.88rem;color:#b89c14">专栏<sup>35</sup></a><a href="/tags/%E4%BA%8B%E5%8A%A1/" style="font-size:.88rem;color:#922401">事务<sup>1</sup></a><a href="/tags/%E4%BC%98%E5%8C%96/" style="font-size:.88rem;color:#437d09">优化<sup>1</sup></a><a href="/tags/%E5%91%BD%E4%BB%A4/" style="font-size:.88rem;color:#705b06">命令<sup>57</sup></a><a href="/tags/%E5%AE%89%E8%A3%85/" style="font-size:.88rem;color:#06a544">安装<sup>6</sup></a><a href="/tags/%E5%B7%A5%E5%85%B7/" style="font-size:.88rem;color:#c86c4f">工具<sup>2</sup></a><a href="/tags/%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B/" style="font-size:.88rem;color:#560b2c">数据类型<sup>8</sup></a><a href="/tags/%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/" style="font-size:.88rem;color:#4e0a32">生命周期<sup>1</sup></a><a href="/tags/%E7%AE%80%E4%BB%8B/" style="font-size:.88rem;color:#230f8e">简介<sup>7</sup></a><a href="/tags/%E7%AE%97%E6%B3%95/" style="font-size:.88rem;color:#c0771e">算法<sup>10</sup></a><a href="/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" style="font-size:.88rem;color:#b40caa">设计模式<sup>38</sup></a><a href="/tags/%E9%85%8D%E7%BD%AE/" style="font-size:.88rem;color:#67389a">配置<sup>2</sup></a><a href="/tags/%E9%9D%A2%E8%AF%95/" style="font-size:.88rem;color:#57425c">面试<sup>11</sup></a></div></div><hr></div></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="anzhiyufont anzhiyu-icon-book-open"></i></button><button id="translateLink" type="button" title="简繁转换">繁</button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="anzhiyufont anzhiyu-icon-arrows-left-right"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="anzhiyufont anzhiyu-icon-gear"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="anzhiyufont anzhiyu-icon-list-ul"></i></button><a id="to_comment" href="#post-comment" title="直达评论"><i class="anzhiyufont anzhiyu-icon-comments"></i></a><button type="button" title="切换背景" onclick="toggleWinbox()"><i class="fas fa-display"></i></button><button id="go-up" type="button" title="回到顶部"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></button></div></div><div id="algolia-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><button class="search-close-button"><i class="anzhiyufont anzhiyu-icon-xmark"></i></button></nav><div class="search-wrap"><div id="algolia-search-input"></div><hr><div id="algolia-search-results"><div id="algolia-hits"></div><div id="algolia-pagination"></div><div id="algolia-info"><div class="algolia-stats"></div><div class="algolia-poweredBy"></div></div></div></div></div><div id="search-mask"></div></div><div id="rightMenu"><div class="rightMenu-group rightMenu-small"><div class="rightMenu-item" id="menu-backward"><i class="anzhiyufont anzhiyu-icon-arrow-left"></i></div><div class="rightMenu-item" id="menu-forward"><i class="anzhiyufont anzhiyu-icon-arrow-right"></i></div><div class="rightMenu-item" id="menu-refresh"><i class="anzhiyufont anzhiyu-icon-arrow-rotate-right" style="font-size:1rem"></i></div><div class="rightMenu-item" id="menu-top"><i class="anzhiyufont anzhiyu-icon-arrow-up"></i></div></div><div class="rightMenu-group rightMenu-line rightMenuPlugin"><div class="rightMenu-item" id="menu-copytext"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制选中文本</span></div><div class="rightMenu-item" id="menu-pastetext"><i class="anzhiyufont anzhiyu-icon-paste"></i><span>粘贴文本</span></div><a class="rightMenu-item" id="menu-commenttext"><i class="anzhiyufont anzhiyu-icon-comment-medical"></i><span>引用到评论</span></a><div class="rightMenu-item" id="menu-newwindow"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开</span></div><div class="rightMenu-item" id="menu-copylink"><i class="anzhiyufont anzhiyu-icon-link"></i><span>复制链接地址</span></div><div class="rightMenu-item" id="menu-copyimg"><i class="anzhiyufont anzhiyu-icon-images"></i><span>复制此图片</span></div><div class="rightMenu-item" id="menu-downloadimg"><i class="anzhiyufont anzhiyu-icon-download"></i><span>下载此图片</span></div><div class="rightMenu-item" id="menu-newwindowimg"><i class="anzhiyufont anzhiyu-icon-window-restore"></i><span>新窗口打开图片</span></div><div class="rightMenu-item" id="menu-search"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>站内搜索</span></div><div class="rightMenu-item" id="menu-searchBaidu"><i class="anzhiyufont anzhiyu-icon-magnifying-glass"></i><span>百度搜索</span></div><div class="rightMenu-item" id="menu-music-toggle"><i class="anzhiyufont anzhiyu-icon-play"></i><span>播放音乐</span></div><div class="rightMenu-item" id="menu-music-back"><i class="anzhiyufont anzhiyu-icon-backward"></i><span>切换到上一首</span></div><div class="rightMenu-item" id="menu-music-forward"><i class="anzhiyufont anzhiyu-icon-forward"></i><span>切换到下一首</span></div><div class="rightMenu-item" id="menu-music-playlist" onclick="window.open(&quot;https://y.qq.com/n/ryqq/playlist/8802438608&quot;, &quot;_blank&quot;);" style="display:none"><i class="anzhiyufont anzhiyu-icon-radio"></i><span>查看所有歌曲</span></div><div class="rightMenu-item" id="menu-music-copyMusicName"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制歌名</span></div></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item menu-link" id="menu-randomPost"><i class="anzhiyufont anzhiyu-icon-shuffle"></i><span>随便逛逛</span></a><a class="rightMenu-item menu-link" href="/categories/"><i class="anzhiyufont anzhiyu-icon-cube"></i><span>博客分类</span></a><a class="rightMenu-item menu-link" href="/tags/"><i class="anzhiyufont anzhiyu-icon-tags"></i><span>文章标签</span></a></div><div class="rightMenu-group rightMenu-line rightMenuOther"><a class="rightMenu-item" id="menu-copy" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-copy"></i><span>复制地址</span></a><a class="rightMenu-item" id="menu-commentBarrage" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-message"></i><span class="menu-commentBarrage-text">关闭热评</span></a><a class="rightMenu-item" id="menu-darkmode" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-circle-half-stroke"></i><span class="menu-darkmode-text">深色模式</span></a><a class="rightMenu-item" id="menu-translate" href="javascript:void(0);" rel="external nofollow noreferrer"><i class="anzhiyufont anzhiyu-icon-language"></i><span>轉為繁體</span></a></div></div><div id="rightmenu-mask"></div><div id="he-plugin-simple"></div><script>var WIDGET={CONFIG:{modules:"0124",background:"2",tmpColor:"FFFFFF",tmpSize:"16",cityColor:"FFFFFF",citySize:"16",aqiColor:"E8D87B",aqiSize:"16",weatherIconSize:"24",alertIconSize:"18",padding:"10px 10px 10px 10px",shadow:"0",language:"auto",borderRadius:"20",fixed:"true",vertical:"top",horizontal:"left",left:"20",top:"7.1",key:"df245676fb434a0691ead1c63341cd94"}}</script><link rel="stylesheet" href="https://widget.qweather.net/simple/static/css/he-simple.css?v=1.4.0"><script src="https://widget.qweather.net/simple/static/js/he-simple.js?v=1.4.0"></script><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://cdn.cbd.int/@fancyapps/ui@5.0.20/dist/fancybox/fancybox.umd.js"></script><script src="https://cdn.cbd.int/instant.page@5.2.0/instantpage.js" type="module"></script><script src="https://cdn.cbd.int/vanilla-lazyload@17.8.4/dist/lazyload.iife.min.js"></script><script src="https://cdn.cbd.int/node-snackbar@0.1.16/dist/snackbar.min.js"></script><script>function panguFn(){"object"==typeof pangu?pangu.autoSpacingPage():getScript("https://cdn.cbd.int/pangu@4.0.7/dist/browser/pangu.min.js").then(()=>{pangu.autoSpacingPage()})}function panguInit(){panguFn()}document.addEventListener("DOMContentLoaded",panguInit)</script><script>var meting_api="https://api.injahow.cn/meting/?server=:server&type=:type&id=:id&auth=:auth&r=:r"</script><canvas id="universe"></canvas><script async src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.0/dark/dark.js"></script><script>var HoldLog=console.log;console.log=function(){};let now1=new Date;queueMicrotask(()=>{function o(){HoldLog.apply(console,arguments)}var c=new Date("09/01/2018 00:00:00"),c=(now1.setTime(now1.getTime()+250),(now1-c)/1e3/60/60/24),c=["欢迎使用安知鱼!","生活明朗, 万物可爱",`
        
       █████╗ ███╗   ██╗███████╗██╗  ██╗██╗██╗   ██╗██╗   ██╗
      ██╔══██╗████╗  ██║╚══███╔╝██║  ██║██║╚██╗ ██╔╝██║   ██║
      ███████║██╔██╗ ██║  ███╔╝ ███████║██║ ╚████╔╝ ██║   ██║
      ██╔══██║██║╚██╗██║ ███╔╝  ██╔══██║██║  ╚██╔╝  ██║   ██║
      ██║  ██║██║ ╚████║███████╗██║  ██║██║   ██║   ╚██████╔╝
      ╚═╝  ╚═╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝    ╚═════╝
        
        `,"已上线",Math.floor(c),"天","©2018 By 安知鱼 V1.6.6"],e=["NCC2-036","调用前置摄像头拍照成功，识别为【小笨蛋】.","Photo captured: ","🤪"];setTimeout(o.bind(console,`
%c${c[0]} %c ${c[1]} %c ${c[2]} %c${c[3]}%c ${c[4]}%c ${c[5]}

%c ${c[6]}
`,"color:#425AEF","","color:#425AEF","color:#425AEF","","color:#425AEF","")),setTimeout(o.bind(console,`%c ${e[0]} %c ${e[1]} %c 
${e[2]} %c
${e[3]}
`,"color:white; background-color:#4fd953","","",'background:url("https://npm.elemecdn.com/anzhiyu-blog@1.1.6/img/post/common/tinggge.gif") no-repeat;font-size:450%')),setTimeout(o.bind(console,"%c WELCOME %c 你好，小笨蛋.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c ⚡ Powered by 安知鱼 %c 你正在访问 云少 的博客.","color:white; background-color:#f0ad4e","")),setTimeout(o.bind(console,"%c W23-12 %c 你已打开控制台.","color:white; background-color:#4f90d9","")),setTimeout(console.warn.bind(console,"%c S013-782 %c 你现在正处于监控中.","color:white; background-color:#d9534f",""))})</script><script async src="/anzhiyu/random.js"></script><script async>!function(){var n,o,r,a,i,e=new Date("09/01/2018 00:00:00"),l=new Date;setInterval(()=>{var t;if(l=new Date,i=l.getHours(),t=(l-e)/1e3/60/60/24,n=Math.floor(t),t=(l-e)/1e3/60/60-24*n,o=Math.floor(t),1==String(o).length&&(o="0"+o),t=(l-e)/1e3/60-1440*n-60*o,r=Math.floor(t),1==String(r).length&&(r="0"+r),t=(l-e)/1e3-86400*n-3600*o-60*r,a=Math.round(t),1==String(a).length&&(a="0"+a),document.getElementById("footer")){let e="";e=(i<18&&9<=i||((t=document.querySelector("#workboard .workSituationImg")).src="https://npm.elemecdn.com/anzhiyu-blog@2.0.4/img/badge/安知鱼-下班啦.svg",t.title="下班了就该开开心心的玩耍，嘿嘿~",t.alt="下班了就该开开心心的玩耍，嘿嘿~"),`本站居然运行了 ${n} 天<span id='runtime'> ${o} 小时 ${r} 分 ${a} 秒 </span><i class='anzhiyufont anzhiyu-icon-heartbeat' style='color:red'></i>`),document.getElementById("runtimeTextTip")&&(document.getElementById("runtimeTextTip").innerHTML=e)}},1e3)}()</script><script src="https://cdn.cbd.int/algoliasearch@4.18.0/dist/algoliasearch-lite.umd.js"></script><script src="https://cdn.cbd.int/instantsearch.js@4.56.5/dist/instantsearch.production.min.js"></script><script src="/js/search/algolia.js"></script><div class="js-pjax"><script>(()=>{const e=document.querySelectorAll("#article-container .mermaid-wrap");if(0!==e.length){const n=()=>{window.loadMermaid=!0;const a="dark"===document.documentElement.getAttribute("data-theme")?"dark":"default";Array.from(e).forEach((e,t)=>{const n=e.firstElementChild;e="%%{init:{ 'theme':'"+a+"'}}%%\n"+n.textContent;const d=mermaid.render("mermaid-"+t,e);"string"==typeof d?(t=d,n.insertAdjacentHTML("afterend",t)):d.then(({svg:e})=>{n.insertAdjacentHTML("afterend",e)})})};var t=()=>{window.loadMermaid?n():getScript("https://cdn.cbd.int/mermaid@10.2.4/dist/mermaid.min.js").then(n)};anzhiyu.addGlobalFn("themeChange",n,"mermaid"),window.pjax?t():document.addEventListener("DOMContentLoaded",t)}})()</script><script>(()=>{const t=()=>{twikoo.init(Object.assign({el:"#twikoo-wrap",envId:"https://twikoo.tryrun.top/",region:"",onCommentLoaded:()=>{anzhiyu.loadLightbox(document.querySelectorAll("#twikoo .tk-content img:not(.tk-owo-emotion)"))}},null))};var o=()=>{"object"==typeof twikoo?setTimeout(n,0):getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(n)};const n=()=>{t()};anzhiyu.loadComment(document.getElementById("twikoo-wrap"),o)})()</script><script>(()=>{const t=()=>{window.artalkItem=new Artalk(Object.assign({el:"#artalk-wrap",server:"https://artalk.tryrun.top/",site:"https://blog.tryrun.top/",pageKey:location.pathname,darkMode:"dark"===document.documentElement.getAttribute("data-theme"),countEl:".artalk-count"},null)),"null"!==GLOBAL_CONFIG.lightbox&&window.artalkItem.use(t=>{t.on("list-loaded",()=>{t.getCommentList().forEach(t=>{t=t.getRender().$content;anzhiyu.loadLightbox(t.querySelectorAll("img:not([atk-emoticon])"))})})})};var a=async()=>{"object"==typeof window.artalkItem||(await getCSS("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.css"),await getScript("https://cdn.cbd.int/artalk@2.5.5/dist/Artalk.js")),t()};anzhiyu.addGlobalFn("themeChange",t=>{var a=document.getElementById("artalk-wrap");a&&a.children.length&&window.artalkItem.setDarkMode("dark"===t)},"artalk"),window.loadOtherComment=a})()</script><input type="hidden" name="page-type" id="page-type" value="post"></div><script>window.addEventListener("load",()=>{const t=e=>e=""!==e&&150<(e=(e=(e=(e=e.replace(/<img.*?src="(.*?)"?[^\>]+>/gi,"[图片]")).replace(/<a[^>]+?href=["']?([^"']+)["']?[^>]*>([^<]+)<\/a>/gi,"[链接]")).replace(/<pre><code>.*?<\/pre>/gi,"[代码]")).replace(/<[^>]+>/g,"")).length?e.substring(0,150)+"...":e,n=t=>{let n="";if(t.length)for(let e=0;e<t.length;e++)n=(n=(n+="<div class='aside-list-item'>")+`<a href='${t[e].url}' class='thumbnail'><img data-lazy-src='${t[e].avatar}' alt='${t[e].nick}'><div class='name'><span>${t[e].nick} </span></div></a>`)+`<div class='content'>
        <a class='comment' href='${t[e].url}' title='${t[e].content}'>${t[e].content}</a>
        <time datetime="${t[e].date}">${anzhiyu.diffDate(t[e].date,!0)}</time></div>
        </div>`;else n+="没有评论";var e=document.querySelector("#card-newest-comments .aside-list");e.innerHTML=n,window.lazyLoadInstance&&window.lazyLoadInstance.update(),window.pjax&&window.pjax.refresh(e)};var e=()=>{var e;document.querySelector("#card-newest-comments .aside-list")&&((e=saveToLocal.get("twikoo-newest-comments"))?n(JSON.parse(e)):(e=()=>{twikoo.getRecentComments({envId:"https://twikoo.tryrun.top/",region:"",pageSize:6,includeReply:!0}).then(function(e){e=e.map(e=>({content:t(e.comment),avatar:e.avatar,nick:e.nick,url:e.url+"#"+e.id,date:new Date(e.created).toISOString()}));saveToLocal.set("twikoo-newest-comments",JSON.stringify(e),10/1440),n(e)}).catch(function(e){document.querySelector("#card-newest-comments .aside-list").textContent="无法获取评论，请确认相关配置是否正确"})},"object"==typeof twikoo?e():getScript("https://cdn.cbd.int/twikoo@1.6.18/dist/twikoo.all.min.js").then(e)))};e(),document.addEventListener("pjax:complete",e)})</script><script async data-pjax src="https://npm.elemecdn.com/anzhiyu-theme-static@1.0.1/bubble/bubble.js"></script><script>var visitorMail="visitor@anheyu.com"</script><script async data-pjax src="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/waterfall/waterfall.js"></script><script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/qrcodejs/1.0.0/qrcode.min.js"></script><script src="/js/anzhiyu/right_click_menu.js"></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.1.9/icon/ali_iconfont_css.css"><script async src="/js/1.min.js?1"></script><script async src="/js/bg.js?1"></script><script async src="https://cdn1.tianli0.top/npm/sweetalert2@8.19.0/dist/sweetalert2.all.js"></script><script async src="/js/lunar.min.js?1"></script><script async src="/js/day.min.js?1"></script><script async src="/js/winbox.bundle.min.js?1"></script><script id="click-show-text" src="https://cdn.cbd.int/butterfly-extsrc@1.1.3/dist/click-show-text.min.js" data-mobile="true" data-text="富强,民主,文明,和谐,自由,平等,公正,法治,爱国,敬业,诚信,友善" data-fontsize="15px" data-random="true" async></script><link rel="stylesheet" href="https://cdn.cbd.int/anzhiyu-theme-static@1.0.0/aplayer/APlayer.min.css" media="print" onload='this.media="all"'><script src="https://cdn.cbd.int/anzhiyu-blog-static@1.0.1/js/APlayer.min.js"></script><script src="https://cdn.cbd.int/hexo-anzhiyu-music@1.0.1/assets/js/Meting2.min.js"></script><script src="https://cdn.cbd.int/pjax@0.2.8/pjax.min.js"></script><script>let pjaxSelectors=['meta[property="og:image"]','meta[property="og:title"]','meta[property="og:url"]','meta[property="og:type"]','meta[property="og:site_name"]','meta[property="og:description"]',"head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"];var pjax=new Pjax({elements:'a:not([target="_blank"])',selectors:pjaxSelectors,cacheBust:!1,analytics:!0,scrollRestoration:!1});document.addEventListener("pjax:send",function(){if(anzhiyu.removeGlobalFnEvent("pjax"),anzhiyu.removeGlobalFnEvent("themeChange"),document.getElementById("rightside").classList.remove("rightside-show"),window.aplayers)for(let e=0;e<window.aplayers.length;e++)window.aplayers[e].options.fixed||window.aplayers[e].destroy();"object"==typeof typed&&typed.destroy();var e=document.body.classList;e.contains("read-mode")&&e.remove("read-mode")}),document.addEventListener("pjax:complete",function(){window.refreshFn(),document.querySelectorAll("script[data-pjax]").forEach(e=>{const t=document.createElement("script");var a=e.text||e.textContent||e.innerHTML||"";Array.from(e.attributes).forEach(e=>t.setAttribute(e.name,e.value)),t.appendChild(document.createTextNode(a)),e.parentNode.replaceChild(t,e)}),GLOBAL_CONFIG.islazyload&&window.lazyLoadInstance.update(),"function"==typeof chatBtnFn&&chatBtnFn(),"function"==typeof panguInit&&panguInit(),"function"==typeof gtag&&gtag("config","G-3VMKW5TZBM",{page_path:window.location.pathname}),"object"==typeof _hmt&&_hmt.push(["_trackPageview",window.location.pathname]),"function"==typeof loadMeting&&document.getElementsByClassName("aplayer").length&&loadMeting(),"object"==typeof Prism&&Prism.highlightAll()}),document.addEventListener("pjax:error",e=>{404===e.request.status&&pjax.loadUrl("/404.html")})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script><script charset="UTF-8" src="https://cdn.cbd.int/anzhiyu-theme-static@1.1.5/accesskey/accesskey.js"></script></div><div id="popup-window"><div class="popup-window-title">通知</div><div class="popup-window-divider"></div><div class="popup-window-content"><div class="popup-tip">你好呀</div><div class="popup-link"><i class="anzhiyufont anzhiyu-icon-arrow-circle-right"></i></div></div></div></body></html>